diff options
Diffstat (limited to 'src/server/server.cc')
| -rw-r--r-- | src/server/server.cc | 470 |
1 files changed, 314 insertions, 156 deletions
diff --git a/src/server/server.cc b/src/server/server.cc index 72faa03..4592334 100644 --- a/src/server/server.cc +++ b/src/server/server.cc @@ -2,25 +2,39 @@ namespace server { -static proto::packet -make_init_packet(const resources::client_map_value& client) noexcept { - proto::packet packet; - - const auto init_packet = packet.mutable_init_packet(); - init_packet->set_seed(state.seed); - init_packet->set_draw_distance(state.draw_distance); - shared::net::set_player(*init_packet->mutable_localplayer(), - client->get_player()); +static std::uint32_t& get_tick() noexcept { + static std::uint32_t ret = 0; + return ret; +} +static proto::player make_player_packet(const shared::player& player) noexcept { + proto::player packet; + player.pack(&packet); return packet; } -static proto::packet make_player_packet(const shared::player& player) noexcept { +static proto::packet +make_animate_update_packet(const shared::animate& animate, const std::optional<shared::tick_t>& sequence) noexcept { proto::packet packet; + const auto animate_update = packet.mutable_animate_update_packet(); + animate.pack(animate_update->mutable_animate()); + animate_update->set_tick(get_tick()); + if (sequence.has_value()) { + animate_update->set_sequence(*sequence); + } + return packet; +} - const auto player_packet = packet.mutable_player_packet(); - shared::net::set_player(*player_packet, player); +static proto::packet +make_init_packet(const resources::client_map_value& client) noexcept { + proto::packet packet; + const auto init_packet = packet.mutable_init_packet(); + init_packet->set_seed(state.seed); + init_packet->set_draw_distance(state.draw_distance); + init_packet->set_tickrate(state.tickrate); + init_packet->set_tick(get_tick()); + (*client->player_info)->player.pack(init_packet->mutable_localplayer()); return packet; } @@ -39,7 +53,7 @@ static proto::packet make_remove_packet(const resources::client_map_value& client) noexcept { proto::packet packet; - const auto remove_packet = packet.mutable_remove_player_packet(); + const auto remove_packet = packet.mutable_remove_entity_packet(); remove_packet->set_index(client->index); return packet; @@ -62,12 +76,11 @@ static proto::packet make_chunk_packet(const world::chunk& chunk) noexcept { static void block_by_tickrate() noexcept { const auto tickrate = server::state.tickrate; - static const auto ratetime = std::chrono::milliseconds( - static_cast<int>((1.0 / static_cast<double>(tickrate)) * 1000.0)); - static auto prev = std::chrono::steady_clock::now(); - const auto now = std::chrono::steady_clock::now(); - std::this_thread::sleep_for(ratetime - (now - prev)); - prev = std::chrono::steady_clock::now(); + static const auto ratetime = std::chrono::microseconds( + static_cast<int>((1.0 / static_cast<double>(tickrate)) * 1'000'000)); + static auto wanted = std::chrono::steady_clock::now(); + std::this_thread::sleep_until(wanted); + wanted += ratetime; } // Creates, binds, listens on new nonblocking socket. @@ -89,14 +102,52 @@ static std::optional<shared::net::connection> make_connection(const int sock) { if (!accept.has_value()) { return std::nullopt; } - return shared::net::connection(accept->socket); + + try { + return shared::net::connection(accept->socket); + } catch (const std::runtime_error& e) { +#ifndef NDEBUG + shared::print::debug + << shared::print::time + << "server: constructor for client connection failed; what(): " + << e.what() << '\n'; +#endif + } + + return std::nullopt; +} + +static void move_client(resources::client_map_value& client, + resources::chunk_map& chunks) noexcept { + movement::move(*client, chunks); } static void handle_move_packet(const proto::move& packet, - resources::client_map_value& client) noexcept { - client->get_player().viewangles = {.pitch = packet.viewangles().pitch(), - .yaw = packet.viewangles().yaw()}; - client->get_player().commands = packet.commands(); + resources::client_map_value& client, + resources::chunk_map& chunks) noexcept { + if (!client->has_initialised()) { + return; + } + + const shared::tick_t sequence = packet.sequence(); + if (sequence <= client->sequence) { // Packet is late, drop it. +#ifndef NDEBUG + shared::print::debug << shared::print::time + << "server: client sent late tick " << sequence + << " <= " << client->sequence << '\n'; +#endif + return; + } + client->sequence = sequence; + + auto& player = client->get_player(); + player.get_mutable_angles() = {.pitch = packet.viewangles().pitch(), + .yaw = packet.viewangles().yaw()}; + player.get_mutable_angles().clamp(); + player.get_mutable_commands() = packet.commands(); + player.get_mutable_active_item() = packet.active_item(); + + move_client(client, chunks); } static void handle_say_packet(const proto::say& packet, @@ -104,18 +155,24 @@ static void handle_say_packet(const proto::say& packet, server::resources::client_map& clients) noexcept { if (std::size(packet.text()) > shared::MAX_SAY_LENGTH) { #ifndef NDEBUG - shared::print::warn( - "server: client tried to say a message that was too long, size: " + - std::to_string(std::size(packet.text())) + '\n'); + shared::print::debug + << shared::print::time + << "server: client tried to say a message that was too long, size: " + << std::size(packet.text()) << '\n'; #endif return; } - shared::print::message("server: player " + std::to_string(client->index) + - " said \"" + packet.text() + "\"\n"); - const auto hear_packet = make_hear_packet(packet.text(), client->index); + shared::print::message << shared::print::time << "server: player " + << client->index << " said \"" << packet.text() + << "\"\n"; + const auto hear_packet = std::make_shared<shared::net::rpacket>( + make_hear_packet(packet.text(), client->index)); for (auto& [index, client_ptr] : clients) { + if (!client_ptr->is_in_pvs(*client)) { + continue; + } client_ptr->connection.rsend_packet(hear_packet); } } @@ -145,9 +202,9 @@ static void send_chunk_associated(resources::chunk_map_value& chunk_data, // correctly. if (find_it == std::end(clients)) { #ifndef NDEBUG - shared::print::debug( - "client index " + std::to_string(client_index) + - " was associated with a chunk, but not found\n"); + shared::print::debug + << shared::print::time << "client index " << client_index + << " was associated with a chunk, but not found\n"; #endif continue; } @@ -209,33 +266,45 @@ static void handle_request_chunk_packet(const proto::request_chunk& packet, resources::client_map_value& client, resources::chunk_map& chunks, resources::pool_t& pool) noexcept { + if (!client->has_initialised()) { + return; + } const shared::math::coords coords{packet.chunk_pos().x(), packet.chunk_pos().z()}; if (const auto find_it = chunks.find(coords); find_it != std::end(chunks)) { auto& chunk_data = find_it->second; + + // Associate client, then post sending of chunk to client. resources::associate_client_chunk(coords, client, chunk_data); boost::asio::post( - pool, - std::bind( - [](const shared::math::coords coords, - const shared::player::index_t index) { - auto res_lock = resources::get_resources_lock(); - auto& chunk_data = res_lock->chunks.find(coords)->second; - - if (!chunk_data->has_initialised()) { - return; // will be sent on construction - } - - const auto& client_it = res_lock->clients.find(index); - if (client_it == std::end(res_lock->clients)) { - return; - } - auto& client = client_it->second; - send_chunk(chunk_data->get_chunk(), client); - maybe_post_chunk_rm(coords, chunk_data, res_lock->pool); - }, - coords, client->index)); + pool, std::bind( + [](const shared::math::coords coords, + const shared::player::index_t index) { + auto res_lock = resources::get_resources_lock(); + + // Client has requested a chunk removed before + // receiving it? + const auto find_it = res_lock->chunks.find(coords); + if (find_it == std::end(res_lock->chunks)) { + return; + } + auto& chunk_data = find_it->second; + + if (!chunk_data->has_initialised()) { + return; // will be sent on construction + } + + const auto& client_it = res_lock->clients.find(index); + if (client_it == std::end(res_lock->clients)) { + return; + } + auto& client = client_it->second; + send_chunk(chunk_data->get_chunk(), client); + maybe_post_chunk_rm(coords, chunk_data, + res_lock->pool); + }, + coords, client->index)); return; } @@ -247,19 +316,26 @@ static void handle_request_chunk_packet(const proto::request_chunk& packet, resources::associate_client_chunk(coords, client, data); boost::asio::post( - pool, - std::bind( - [](const shared::math::coords coords) { - server::world::chunk chunk{server::state.seed, coords}; + pool, std::bind( + [](const shared::math::coords coords) { + auto chunk = + [&coords]() -> std::shared_ptr<server::world::chunk> { + auto maybe_chunk = database::maybe_read_chunk(coords); + if (maybe_chunk.has_value()) { + return std::make_shared<server::world::chunk>( + server::state.seed, *maybe_chunk); + } + return std::make_shared<server::world::chunk>( + server::state.seed, coords); + }(); - auto res_lock = resources::get_resources_lock(); - auto& chunk_data = res_lock->chunks.find(coords)->second; - chunk_data->chunk.emplace( - std::make_unique<server::world::chunk>(std::move(chunk))); - send_chunk_associated(chunk_data, res_lock->clients); - maybe_post_chunk_rm(coords, chunk_data, res_lock->pool); - }, - coords)); + auto res_lock = resources::get_resources_lock(); + auto& chunk_data = res_lock->chunks.find(coords)->second; + chunk_data->chunk.emplace(std::move(chunk)); + send_chunk_associated(chunk_data, res_lock->clients); + maybe_post_chunk_rm(coords, chunk_data, res_lock->pool); + }, + coords)); } // Disassociates a client with a chunk, while performing necessary cleanups. @@ -310,17 +386,21 @@ static void post_chunk_update(const shared::math::coords& coords, coords)); } -static void modify_block(const enum shared::world::block::type block_type, - const glm::ivec3& block_pos, - const shared::math::coords& coords, - resources::chunk_map& chunks) noexcept { +static void +handle_add_block_packet(const proto::add_block& packet, + [[maybe_unused]] resources::client_map_value& client, + server::resources::chunk_map& chunks, + resources::pool_t& pool) noexcept { + + const auto coords = shared::net::get_coords(packet.chunk_pos()); + const auto pos = shared::net::get_ivec3(packet.block_pos()); + const auto active_item = packet.active_item(); const auto find_it = chunks.find(coords); if (find_it == std::end(chunks)) { return; } - - if (shared::world::chunk::is_outside_chunk(block_pos)) { + if (shared::world::chunk::is_outside_chunk(pos)) { return; } @@ -329,22 +409,24 @@ static void modify_block(const enum shared::world::block::type block_type, return; } - chunk_data->get_chunk().get_block(block_pos) = block_type; - chunk_data->get_chunk().arm_should_update(); -} + auto& inventory = client->get_player().inventory; + if (active_item < 0 || active_item >= std::size(inventory.contents)) { + return; + } -static void -handle_add_block_packet(const proto::add_block& packet, - [[maybe_unused]] resources::client_map_value& client, - server::resources::chunk_map& chunks, - resources::pool_t& pool) noexcept { - const shared::math::coords coords{packet.chunk_pos().x(), - packet.chunk_pos().z()}; - const glm::ivec3 block_pos{packet.block_pos().x(), packet.block_pos().y(), - packet.block_pos().z()}; - const auto block = - static_cast<enum shared::world::block::type>(packet.block()); - modify_block(block, block_pos, coords, chunks); + const auto& item = inventory.contents[active_item]; + if (item == nullptr) { + return; + } + const auto block_ptr = dynamic_cast<shared::item::block*>(&*item); + if (block_ptr == nullptr) { + return; + } + + chunk_data->get_chunk().get_block(pos) = block_ptr->type; + inventory.decrement(active_item); + + chunk_data->get_chunk().arm_should_update(); post_chunk_update(coords, pool); } @@ -353,11 +435,30 @@ handle_remove_block_packet(const proto::remove_block& packet, [[maybe_unused]] resources::client_map_value& client, resources::chunk_map& chunks, resources::pool_t& pool) noexcept { - const shared::math::coords coords{packet.chunk_pos().x(), - packet.chunk_pos().z()}; - const glm::ivec3 block_pos{packet.block_pos().x(), packet.block_pos().y(), - packet.block_pos().z()}; - modify_block(shared::world::block::type::air, block_pos, coords, chunks); + const auto coords = shared::net::get_coords(packet.chunk_pos()); + const auto pos = shared::net::get_ivec3(packet.block_pos()); + + const auto find_it = chunks.find(coords); + if (find_it == std::end(chunks)) { + return; + } + if (shared::world::chunk::is_outside_chunk(pos)) { + return; + } + + auto& chunk_data = find_it->second; + if (!chunk_data->has_initialised()) { + return; + } + + auto& block = chunk_data->get_chunk().get_block(pos); + + auto& inventory = client->get_player().inventory; + inventory.maybe_add(shared::item::block::get_type(block.type), 1); + + block = shared::world::block::type::air; + + chunk_data->get_chunk().arm_should_update(); post_chunk_update(coords, pool); } @@ -379,47 +480,58 @@ static void handle_auth_packet(const proto::auth& packet, auto& client = find_it->second; // find if we're already associated, eventually kick our client - // it would be nice if this wasn't a find_if - if (std::find_if(std::begin(res_lock->clients), - std::end(res_lock->clients), - [&](const auto& it) { - auto& client = it.second; - if (!client->has_initialised()) { - return false; - } - return client->index != client_index && - client->get_username() == user; - }) != std::end(res_lock->clients)) { - + if (std::ranges::any_of(res_lock->clients, + [&client_index, &user](const auto& it) { + const auto& [idx, c] = it; + if (idx == client_index) { + return false; + } + if (!c->has_initialised()) { + return false; + } + if (c->get_username() != user) { + return false; + } + return true; + })) { client->disconnect_reason.emplace("user already in server"); return; } - struct client::player_info player_info { - .username = user, .password = pass - }; - if (db_plr.has_value()) { - auto& [player, db_password] = *db_plr; + const auto in_db = db_plr.has_value(); + if (in_db && db_plr->second != pass) { + client->disconnect_reason.emplace("bad password"); + return; + } - if (db_password != pass) { - client->disconnect_reason.emplace("bad password"); - return; + shared::player player = [&]() -> shared::player { + if (in_db) { + // update to new client_index before returning + shared::player player{db_plr->first}; + player.get_mutable_index() = client_index; + return player; } - - player_info.player = shared::net::get_player(player); - player_info.player.index = client_index; - } else { // TODO: Find a random spawn chunk and put the player // on the highest block. Because we don't want to gen chunks // while blocking, we can't do this here. For now, we'll // just put the player at a high spot. - player_info.player = - shared::player{.index = client_index, - .local_pos = {0.0f, 140.0f, 0.0f}}; - } + return shared::player{ + shared::item::items{}, + 0u, + shared::math::angles{0.0f, 0.0f}, + glm::vec3{0.0f, 0.0f, 0.0f}, + 0u, + client_index, + shared::math::coords{0, 0}, + glm::vec3{0.0f, 120.0f, 0.0f}, + }; + }(); + + using pi = struct client::player_info; client->player_info.emplace( - std::make_shared<struct client::player_info>( - std::move(player_info))); + std::make_shared<pi>(pi{.username = user, + .password = pass, + .player = std::move(player)})); client->connection.rsend_packet(make_init_packet(client)); }, @@ -427,6 +539,30 @@ static void handle_auth_packet(const proto::auth& packet, std::move(packet.password()))); } +static void +handle_item_swap_packet(const proto::item_swap& proto, + const resources::client_map_value& client) noexcept { + auto& inventory = client->get_player().inventory; + + const auto a = proto.index_a(); + const auto b = proto.index_b(); + + if (a == b) { + return; + } + const auto in_range = [&](const auto val) { + const auto MAX_SIZE = + static_cast<std::uint32_t>(std::size(inventory.contents)); + return std::clamp(val, 0u, MAX_SIZE) == val; + }; + + if (!in_range(a) || !in_range(b)) { + return; + } + + std::swap(inventory.contents[a], inventory.contents[b]); +} + // Get new packets from clients, this will change client data and worldata. static void parse_client_packets(resources::client_map_value& client, resources::resources& res) noexcept { @@ -434,7 +570,7 @@ static void parse_client_packets(resources::client_map_value& client, if (packet->has_auth_packet()) { handle_auth_packet(packet->auth_packet(), client, res.pool); } else if (packet->has_move_packet()) { - handle_move_packet(packet->move_packet(), client); + handle_move_packet(packet->move_packet(), client, res.chunks); } else if (packet->has_say_packet()) { handle_say_packet(packet->say_packet(), client, res.clients); } else if (packet->has_request_chunk_packet()) { @@ -449,10 +585,13 @@ static void parse_client_packets(resources::client_map_value& client, } else if (packet->has_remove_block_packet()) { handle_remove_block_packet(packet->remove_block_packet(), client, res.chunks, res.pool); + } else if (packet->has_item_swap_packet()) { + handle_item_swap_packet(packet->item_swap_packet(), client); } #ifndef NDEBUG else { - shared::print::warn("server: unhandled packet type\n"); + shared::print::debug << shared::print::time + << "server: unhandled packet type\n"; } #endif } @@ -462,7 +601,8 @@ static void parse_client_packets(resources::client_map_value& client, static void send_remove_packets(server::resources::client_map_value& remove_client, server::resources::client_map& clients) noexcept { - const auto remove_packet = make_remove_packet(remove_client); + const auto remove_packet = std::make_shared<shared::net::rpacket>( + make_remove_packet(remove_client)); for (auto& [index, client_ptr] : clients) { client_ptr->connection.rsend_packet(remove_packet); } @@ -470,8 +610,9 @@ send_remove_packets(server::resources::client_map_value& remove_client, static void handle_new_connections(shared::net::connection& connection, resources::resources& res) noexcept { - shared::print::message("server: got connection from " + - connection.get_address() + '\n'); + shared::print::message << shared::print::time + << "server: got connection from " + << connection.get_address() << '\n'; // Add the client, waiting for an auth packet. static uint32_t index = 0; @@ -481,25 +622,25 @@ static void handle_new_connections(shared::net::connection& connection, ++index; } -static void move_client(resources::client_map_value& client, - resources::chunk_map& chunks) noexcept { - // shared::movement::fly(client.player, client.commands); - movement::move(*client, chunks); -} - static void send_client_packets(resources::client_map_value& client, resources::resources& res) noexcept { - // TODO PVS, as simple as checking if a client has our chunk pos in their - // associated chunks list + + if (!client->has_initialised()) { + return; + } + const auto non_local_packet = std::make_shared<shared::net::upacket>( + make_animate_update_packet(client->get_player(), std::nullopt)); for (auto& [index, c] : res.clients) { - if (!c->has_initialised()) { - continue; - } - if (!client->chunks.contains(c->get_player().chunk_pos)) { + if (!c->is_in_pvs(*client)) { continue; } - client->connection.usend_packet(make_player_packet(c->get_player())); + // only network the last received sequence if sending an animate packet for the client's local player + if (c->index == client->index) { + c->connection.usend_packet( make_animate_update_packet(client->get_player(), client->sequence) ); + } else { + c->connection.usend_packet(non_local_packet); + } } } @@ -510,15 +651,17 @@ get_sent_disconnect_reason(const resources::client_map_value& client) noexcept { return client->connection.get_bad_reason(); } - if (client->chunks.size() > - unsigned(state.draw_distance * state.draw_distance * 4)) { + if (static const unsigned max = static_cast<unsigned>( + state.draw_distance * state.draw_distance * 4); + client->chunks.size() > max) { return "too many chunks associated with client"; } return client->disconnect_reason; } -static void remove_bad_clients(resources::resources& res) noexcept { +static void remove_bad_clients(resources::resources& res, + const bool send_remove = true) noexcept { for (auto& [index, client] : res.clients) { const auto reason = get_sent_disconnect_reason(client); if (reason == std::nullopt) { @@ -530,11 +673,18 @@ static void remove_bad_clients(resources::resources& res) noexcept { } client->disconnecting = true; - shared::print::message("server: dropped " + - client->connection.get_address() + " for \"" + - *reason + "\"\n"); - send_message("disconnected for " + *reason, client, true); - send_remove_packets(client, res.clients); + shared::print::message << shared::print::time << "server: dropped " + << client->connection.get_address() << " for \"" + << *reason << "\"\n"; + send_message(*reason, client, true); + if (send_remove) { + send_remove_packets(client, res.clients); + } + + // Close early so the client may reconnect and receive a better message + // than a generic errno error if they connect too fast later. + client->connection.poll(); + client->connection.close(); boost::asio::post( res.pool, @@ -546,8 +696,7 @@ static void remove_bad_clients(resources::resources& res) noexcept { if (plr_info.has_value()) { database::write_player( (*plr_info)->username, (*plr_info)->password, - make_player_packet((*plr_info)->player) - .player_packet()); + make_player_packet((*plr_info)->player)); } // cleanup associated chunks @@ -583,9 +732,10 @@ static void process_resources(resources::resources& res) noexcept { } // Move clients via their (hopefully updated) command. - for (auto& [index, client] : res.clients) { + // CHANGED: we now do this when we get move packets + /*for (auto& [index, client] : res.clients) { move_client(client, res.chunks); - } + }*/ // Send packets which are sent once per tick, as of now the player // struct of other clients. @@ -593,9 +743,15 @@ static void process_resources(resources::resources& res) noexcept { send_client_packets(client, res); } + // Send queued packets. + for (auto& [index, client] : res.clients) { + client->connection.poll(); + } + // Delete bad connections, print if it happens. Also sends a remove // to all clients per client removed. remove_bad_clients(res); + ++get_tick(); } void main(const std::string_view address, const std::string_view port) { @@ -603,8 +759,8 @@ void main(const std::string_view address, const std::string_view port) { server::resources::init(); has_initialised = true; - shared::print::notify("server: started at " + std::string{address} + ':' + - std::string{port} + '\n'); + shared::print::notify << shared::print::time << "server: started at " + << address << ':' << port << '\n'; // Server has a tickrate, we will use non-blocking polling at this // tickrate. @@ -629,12 +785,14 @@ void main(const std::string_view address, const std::string_view port) { const auto& client = it.second; client->disconnect_reason.emplace("server shutting down"); }); - remove_bad_clients(*res_lock); + remove_bad_clients(*res_lock, false); } - shared::print::notify("server: writing world data\n"); + shared::print::notify << shared::print::time + << "server: writing world data\n"; server::resources::quit(); - shared::print::notify("server: gracefully exited\n"); + shared::print::notify << shared::print::time + << "server: gracefully exited\n"; } } // namespace server |
