diff options
| author | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-12 21:57:46 +1100 |
|---|---|---|
| committer | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-12 21:57:46 +1100 |
| commit | e4483eca01b48b943cd0461e24a74ae1a3139ed4 (patch) | |
| tree | ed58c3c246e3af1af337697695d780aa31f6ad9a /src/client/client.cc | |
| parent | 1cc08c51eb4b0f95c30c0a98ad1fc5ad3459b2df (diff) | |
Update to most recent version (old initial commit)
Diffstat (limited to 'src/client/client.cc')
| -rw-r--r-- | src/client/client.cc | 690 |
1 files changed, 440 insertions, 250 deletions
diff --git a/src/client/client.cc b/src/client/client.cc index b4cacdc..28b3163 100644 --- a/src/client/client.cc +++ b/src/client/client.cc @@ -1,29 +1,29 @@ #include "client.hh" namespace { -bool should_send_move = false; // Ratelimit move packets by player packets. +std::queue<proto::chunk> received_chunks; -shared::world::block last_block = shared::world::block::type::dirt; - -client::world::chunk::map chunks{4096, shared::world::chunk::hash, - shared::world::chunk::equal}; - -client::players players; } // namespace namespace client { -static auto get_player_it(const std::uint32_t index) { - return std::ranges::find_if( - ::players, [&](const auto& player) { return player.index == index; }); +static shared::net::connection make_connection(const std::string_view address, + const std::string_view port) { + constexpr addrinfo hints = {.ai_flags = AI_PASSIVE, + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM}; + const auto info = shared::net::get_addr_info(address, port, &hints); + const auto sock = shared::net::make_socket(info.get()); + shared::net::connect_socket(sock, info.get()); + return shared::net::connection(sock); } static proto::packet -make_get_chunk_packet(const shared::math::coords& coords) noexcept { +make_request_chunk_packet(const shared::math::coords& coords) noexcept { proto::packet packet; const auto chunk_packet = packet.mutable_request_chunk_packet(); - shared::net::set_coords(*chunk_packet->mutable_chunk_pos(), coords); + shared::net::set_coords(chunk_packet->mutable_chunk_pos(), coords); return packet; } @@ -39,14 +39,16 @@ static proto::packet make_auth_packet(const std::string& username, return packet; } -static proto::packet make_add_block_packet(const shared::math::coords& coords, - const glm::ivec3& pos) noexcept { +static proto::packet +make_add_block_packet(const shared::math::coords& coords, const glm::ivec3& pos, + const std::uint32_t& active) noexcept { proto::packet packet; - const auto interact_packet = packet.mutable_add_block_packet(); - shared::net::set_coords(*interact_packet->mutable_chunk_pos(), coords); - shared::net::set_ivec3(*interact_packet->mutable_block_pos(), pos); - interact_packet->set_block(static_cast<std::uint32_t>(::last_block.type)); + const auto add_block_packet = packet.mutable_add_block_packet(); + shared::net::set_coords(add_block_packet->mutable_chunk_pos(), coords); + shared::net::set_ivec3(add_block_packet->mutable_block_pos(), pos); + + add_block_packet->set_active_item(active); return packet; } @@ -56,9 +58,9 @@ make_remove_block_packet(const shared::math::coords& coords, const glm::ivec3& pos) noexcept { proto::packet packet; - const auto interact_packet = packet.mutable_remove_block_packet(); - shared::net::set_coords(*interact_packet->mutable_chunk_pos(), coords); - shared::net::set_ivec3(*interact_packet->mutable_block_pos(), pos); + const auto remove_block_packet = packet.mutable_remove_block_packet(); + shared::net::set_coords(remove_block_packet->mutable_chunk_pos(), coords); + shared::net::set_ivec3(remove_block_packet->mutable_block_pos(), pos); return packet; } @@ -69,93 +71,113 @@ make_remove_chunk_packet(const shared::math::coords& coords) noexcept { proto::packet packet; const auto remove_chunk_packet = packet.mutable_remove_chunk_packet(); - shared::net::set_coords(*remove_chunk_packet->mutable_chunk_pos(), coords); + shared::net::set_coords(remove_chunk_packet->mutable_chunk_pos(), coords); + + return packet; +} + +static proto::packet +make_move_packet(const client::moveable& localplayer) noexcept { + proto::packet packet; + + const auto move_packet = packet.mutable_move_packet(); + move_packet->set_commands(localplayer.get_commands()); + shared::net::set_angles(move_packet->mutable_viewangles(), + localplayer.get_angles()); + move_packet->set_active_item(localplayer.get_active_item()); + move_packet->set_sequence(localplayer.get_latest_sequence()); return packet; } static void handle_init_packet(const proto::init& packet) noexcept { - client::state.seed = packet.seed(); - client::state.draw_distance = - std::min(packet.draw_distance(), - client::settings::get({"video", "draw_distance"}, 32)); - auto& localplayer = packet.localplayer(); - client::state.localplayer = localplayer.index(); + // Draw distance is the std::min, but we make it more verbose. + if (const auto& wanted = state::draw_distance = + settings::get({"video", "draw_distance"}, 32); + wanted != packet.draw_distance()) { + + if (wanted < packet.draw_distance()) { + state::draw_distance = wanted; + } else { + shared::print::warn << shared::print::time + << "client: server supported draw_distance (" + << packet.draw_distance() + << ") less than requested (" << wanted << ")\n"; + state::draw_distance = packet.draw_distance(); + } + } + + state::seed = packet.seed(); + state::tickrate = packet.tickrate(); + state::tick = packet.tick(); + state::delta_ticks = 0.0f; // amount of time passed (in ticks) - ::players.emplace_back(shared::net::get_player(localplayer)); + const auto& localplayer = packet.localplayer(); + const auto& index = localplayer.animate().entity().index(); + + state::localplayer_index = index; + state::entities.emplace(index, std::make_unique<player>(localplayer)); } -static void handle_player_packet(const proto::player& packet) noexcept { - if (std::size(::players) <= 0) { +static void +handle_animate_update_packet(const proto::animate_update& packet) noexcept { + const auto& animate = packet.animate(); + const auto& index = animate.entity().index(); + + const auto entity_it = get_entity_it(index); + if (entity_it == std::end(state::entities)) { + state::entities.emplace(index, std::make_unique<player>(animate)); return; } - const auto player_it = get_player_it(packet.index()); + const auto animate_ptr = dynamic_cast<class animate*>(&*entity_it->second); + if (animate_ptr == nullptr) { + return; + } - shared::player player = shared::net::get_player(packet); + const shared::tick_t tick = packet.tick(); - if (player_it == std::end(::players)) { - ::players.emplace_back(player); + // Localplayer updates time factor and is update is called with the sequence + // number instead of the tick. + if (packet.has_sequence()) { + const shared::tick_t sequence = packet.sequence(); + animate_ptr->update_time_factor(sequence, tick); + animate_ptr->notify(animate, sequence, true); return; } - // If the player packet refers to us, we do not override our localplayer's - // commands or viewangles. Also, we should send a new move packet. - if (auto& lp = get_localplayer(::players); lp.index == packet.index()) { - player.viewangles = lp.viewangles; - player.commands = 0u; - ::should_send_move = true; - } - player_it->update(player); + animate_ptr->notify(animate, tick, true); } // Remove the client whose element is equal to pkt.index. -static void handle_remove_packet(const proto::remove_player& packet) noexcept { - const auto player_it = get_player_it(packet.index()); - if (player_it == std::end(::players)) { +static void +handle_remove_entity_packet(const proto::remove_entity& packet) noexcept { + const auto entity_it = get_entity_it(packet.index()); + if (entity_it == std::end(state::entities)) { return; } - ::players.erase(player_it); + + state::entities.erase(entity_it); } static void handle_hear_packet(const proto::hear_player& packet) noexcept { - const auto player_it = get_player_it(packet.index()); - if (player_it == std::end(::players)) { + const auto entity_it = get_entity_it(packet.index()); + if (entity_it == std::end(state::entities)) { return; } - player_it->message.emplace(packet.text()); -} -static void handle_chunk_packet(const proto::chunk& packet) noexcept { - const shared::math::coords pos{.x = packet.chunk_pos().x(), - .z = packet.chunk_pos().z()}; - const auto find_it = ::chunks.find(pos); - if (find_it == std::end(::chunks)) { + const auto player_ptr = dynamic_cast<player*>(&*entity_it->second); + if (player_ptr == nullptr) { return; } - find_it->second.emplace( - client::state.seed, pos, - shared::world::chunk::make_blocks_from_chunk(packet)); - // Force the surrounding chunks to regenerate their vbos. - // It's possible we could only generate the ones that we actually need to - // generate, but that's not really a priority atm. - for (auto x = -1; x <= 1; ++x) { - for (auto z = -1; z <= 1; ++z) { - if (std::abs(x) == 1 && std::abs(z) == 1) { - continue; - } - const auto find_update_it = - ::chunks.find(pos + shared::math::coords{x, z}); - if (find_update_it == std::end(::chunks)) { - continue; - } - if (!find_update_it->second.has_value()) { - continue; - } - find_update_it->second->should_regenerate_vbo = true; - } - } + + player_ptr->message.emplace(packet.text()); +} + +static void handle_chunk_packet(proto::chunk& packet) noexcept { + // Ratelimited parsing, moved into a vector. + ::received_chunks.push(std::move(packet)); } static void @@ -165,81 +187,111 @@ handle_server_message_packet(const proto::server_message& packet) noexcept { "client: received " + std::string{fatal ? "fatal" : ""} + " message from the server \"" + packet.message() + "\"\n"; if (!fatal) { - shared::print::notify(message); + shared::print::notify << shared::print::time << message; return; } - shared::print::warn(message); + shared::print::warn << shared::print::time << message; shared::should_exit = true; } -static void parse_packet(const proto::packet& packet) noexcept { - +static void parse_packet(proto::packet&& packet) noexcept { if (packet.has_init_packet()) { handle_init_packet(packet.init_packet()); - } else if (packet.has_player_packet()) { - handle_player_packet(packet.player_packet()); - } else if (packet.has_remove_player_packet()) { - handle_remove_packet(packet.remove_player_packet()); + } else if (packet.has_animate_update_packet()) { + handle_animate_update_packet(packet.animate_update_packet()); + } else if (packet.has_remove_entity_packet()) { + handle_remove_entity_packet(packet.remove_entity_packet()); } else if (packet.has_hear_player_packet()) { handle_hear_packet(packet.hear_player_packet()); } else if (packet.has_chunk_packet()) { - handle_chunk_packet(packet.chunk_packet()); + handle_chunk_packet(*packet.mutable_chunk_packet()); } else if (packet.has_server_message_packet()) { handle_server_message_packet(packet.server_message_packet()); } #ifndef NDEBUG else { - shared::print::warn("client: unhandled packet type\n"); + shared::print::warn << shared::print::time + << "client: unhandled packet type\n"; } #endif } -static shared::net::connection make_connection(const std::string_view address, - const std::string_view port) { - constexpr addrinfo hints = {.ai_flags = AI_PASSIVE, - .ai_family = AF_INET, - .ai_socktype = SOCK_STREAM}; - const auto info = shared::net::get_addr_info(address, port, &hints); - const auto sock = shared::net::make_socket(info.get()); - shared::net::connect_socket(sock, info.get()); - return shared::net::connection(sock); +static void regenerate_surrounding_chunks(const shared::math::coords& pos) { + // Force the surrounding chunks to regenerate their vbos. + // It's possible we could only generate the ones that we actually need + // to generate, but that's not really a priority atm. + for (auto x = -1; x <= 1; ++x) { + for (auto z = -1; z <= 1; ++z) { + if (std::abs(x) == 1 && std::abs(z) == 1) { + continue; + } + const auto find_update_it = + state::chunks.find(pos + shared::math::coords{x, z}); + if (find_update_it == std::end(state::chunks)) { + continue; + } + if (!find_update_it->second.has_value()) { + continue; + } + find_update_it->second->should_regenerate_vbo = true; + } + } } -static void update_chunks(shared::net::connection& connection) noexcept { +static void parse_new_chunks() { + // Add new chunks. We have to ratelimit these because the server responds + // so quickly that our frametime dives as it parses these new chunk packets. + constexpr int CHUNKS_PER_FRAME = 1; + for (int i = 0; i < CHUNKS_PER_FRAME && !::received_chunks.empty(); + ++i, ::received_chunks.pop()) { - const auto draw_distance = client::state.draw_distance; - const shared::math::coords& lp_pos = get_localplayer(::players).chunk_pos; + const proto::chunk& packet = ::received_chunks.front(); + const shared::math::coords pos{packet.chunk_pos().x(), + packet.chunk_pos().z()}; - // Remove bad chunks. - std::erase_if(::chunks, [&](const auto& chunk) { - const bool should_erase = - !shared::math::is_inside_draw(chunk.first, lp_pos, draw_distance); + const auto find_it = state::chunks.find(pos); + if (find_it == std::end(state::chunks)) { + continue; + } + find_it->second.emplace(state::seed, packet); + regenerate_surrounding_chunks(pos); + } +} + +static void erase_bad_chunks(const shared::math::coords& lp_pos) { + const auto draw_distance = state::draw_distance; + std::erase_if(state::chunks, [&](const auto& chunk) { + const bool should_erase = !shared::math::coords::is_inside_draw( + chunk.first, lp_pos, draw_distance); if (should_erase) { - connection.rsend_packet(make_remove_chunk_packet(chunk.first)); + state::connection->rsend_packet( + make_remove_chunk_packet(chunk.first)); } return should_erase; }); +} - for (int dist = 0; dist <= client::state.draw_distance; ++dist) { +static void request_new_chunks(const shared::math::coords& lp_pos) { + for (int dist = 0; dist <= state::draw_distance; ++dist) { + const auto draw_distance = state::draw_distance; const auto maybe_add_chunk = [&](const int x, const int z) -> bool { const auto pos = shared::math::coords{x + lp_pos.x, z + lp_pos.z}; - if (!is_inside_draw(pos, lp_pos, draw_distance)) { + if (!shared::math::coords::is_inside_draw(pos, lp_pos, + draw_distance)) { return false; } - if (::chunks.contains(pos)) { + if (state::chunks.contains(pos)) { return false; } - connection.rsend_packet(make_get_chunk_packet(pos)); - ::chunks.emplace(pos, std::nullopt); + state::connection->rsend_packet(make_request_chunk_packet(pos)); + state::chunks.emplace(pos, std::nullopt); return true; }; int x = -dist; int z = dist; - - // Does a spiral pattern, but it's basically unnoticable :( for (int i = 0; i < dist * 2 + 1; ++i) { if (maybe_add_chunk(x, z)) { return; @@ -267,125 +319,162 @@ static void update_chunks(shared::net::connection& connection) noexcept { } } -static void update_state() noexcept { - client::state.player_count = ::players.size(); - client::state.requested_chunk_count = - static_cast<std::uint32_t>(std::ranges::count_if( - ::chunks, [](const auto& c) { return !c.second.has_value(); })); - client::state.networked_chunk_count = - std::size(::chunks) - client::state.requested_chunk_count; -} - -static proto::packet make_say_packet(const std::string& text) noexcept { - proto::packet packet; - - const auto sub_say_packet = packet.mutable_say_packet(); - sub_say_packet->set_text(text); +static void update_chunks() { + parse_new_chunks(); - return packet; -} - -// unfortunate non-static :( -void send_say_packet(const std::string& text) noexcept { - client::state.connection->rsend_packet(make_say_packet(text)); + const shared::math::coords& lp_pos = get_localplayer().get_chunk_pos(); + erase_bad_chunks(lp_pos); + request_new_chunks(lp_pos); } static void handle_button_input() noexcept { - if (input::is_key_pressed(SDLK_z)) { - client::render::camera::get_xfov() = 30.0f; - } else { - client::render::camera::get_xfov() = - client::settings::get({"gameplay", "fov"}, 100.0f); - } // Don't build our movement commands if we're inputting text. if (input::state.typing) { return; } - auto& lp = get_localplayer(::players); - using spm = shared::player::mask; - lp.commands |= spm::forward * input::is_key_pressed(SDLK_w); - lp.commands |= spm::left * input::is_key_pressed(SDLK_a); - lp.commands |= spm::backward * input::is_key_pressed(SDLK_s); - lp.commands |= spm::right * input::is_key_pressed(SDLK_d); - lp.commands |= spm::jump * input::is_key_pressed(SDLK_SPACE); - lp.commands |= spm::crouch * input::is_key_pressed(SDLK_LCTRL); - lp.commands |= spm::sprint * input::is_key_pressed(SDLK_LSHIFT); - lp.commands |= spm::attack * input::is_key_pressed(SDL_BUTTON_LEFT); + auto& commands = get_localplayer().get_mutable_commands(); + commands |= spm::forward * input::is_key_pressed(SDLK_w); + commands |= spm::left * input::is_key_pressed(SDLK_a); + commands |= spm::backward * input::is_key_pressed(SDLK_s); + commands |= spm::right * input::is_key_pressed(SDLK_d); + commands |= spm::jump * input::is_key_pressed(SDLK_SPACE); + commands |= spm::crouch * input::is_key_pressed(SDLK_LCTRL); + commands |= spm::sprint * input::is_key_pressed(SDLK_LSHIFT); + commands |= spm::attack * input::is_key_pressed(SDL_BUTTON_LEFT); } static void update_input() noexcept { - client::input::update(); + input::update(); + + if (input::is_key_pressed(SDLK_z)) { + render::camera::get_xfov() = 30.0f; + } else { + render::camera::get_xfov() = settings::get({"gameplay", "fov"}, 100.0f); + } - if (!client::window::is_open()) { + if (!window::is_open()) { handle_button_input(); } - if (client::input::state.quit) { + if (input::state.quit) { shared::should_exit = true; } } -static proto::packet -make_move_packet(const shared::player& localplayer) noexcept { - proto::packet packet; +static void update_delta_ticks() noexcept { + static shared::time_point_t last = std::chrono::steady_clock::now(); - const auto move_packet = packet.mutable_move_packet(); - move_packet->set_commands(localplayer.commands); - shared::net::set_angles(*move_packet->mutable_viewangles(), - localplayer.viewangles); + const auto now = std::chrono::steady_clock::now(); + const auto delta_ticks = + shared::get_duration_seconds(now - last) / + shared::get_duration_seconds(state::get_time_per_tick()); - return packet; + state::delta_ticks += state::time_factor * delta_ticks; + + last = now; } -static void send_move_packet(shared::net::connection& connection) noexcept { - const auto& localplayer = get_localplayer(::players); - connection.usend_packet(make_move_packet(localplayer)); +static void update_pre_move() { + state::player_count = state::entities.size(); + state::requested_chunk_count = static_cast<std::uint32_t>( + std::ranges::count_if(state::chunks, [](const auto& c) { + return !c.second.has_value(); + })); + state::networked_chunk_count = + std::size(state::chunks) - state::requested_chunk_count; + update_delta_ticks(); + update_input(); + update_chunks(); } -static void update_players() noexcept { - const auto lp_index = get_localplayer(::players).index; +static void interp_entities() { + for (auto& [index, entity] : state::entities) { + const auto animate_ptr = dynamic_cast<animate*>(&*entity); + if (animate_ptr == nullptr) { + continue; + } + animate_ptr->interpolate(); + } +} + +static void remove_bad_entities() { + const auto lp_index = *state::localplayer_index; // pvs, remove clients outside of chunks we own - std::erase_if(::players, [&](const auto& player) { - if (player.index == lp_index) { + std::erase_if(state::entities, [&](const auto& pair) { + const auto& [idx, entity] = pair; + + const auto player_ptr = dynamic_cast<const player*>(&*entity); + if (player_ptr == nullptr) { + return false; + } + + if (player_ptr->get_index() == lp_index) { return false; } // Players should be removed if the chunk can't draw. - const auto chunk_it = ::chunks.find(player.chunk_pos); - if (chunk_it == std::end(::chunks)) { + const auto chunk_it = state::chunks.find(player_ptr->get_chunk_pos()); + if (chunk_it == std::end(state::chunks)) { return true; } const auto& chunk = chunk_it->second; - if (!chunk.has_value()) { - return false; - } - - if (!chunk->can_draw()) { + if (chunk.has_value() && !chunk->can_draw()) { return true; } return false; }); } +static void update_entities() noexcept { + interp_entities(); + remove_bad_entities(); +} + +static void update_post_move() { update_entities(); } + +static void maybe_send_move() noexcept { + + // An unknown amount of time has passed since we last rendered and this may + // be more than one tick. We do nothing if it's less than one tick. + const auto num_ticks_passed = static_cast<unsigned>(state::delta_ticks); + + if (num_ticks_passed <= 0) { + return; + } + + // Extrapolate for the number of ticks passed. + auto& localplayer = get_localplayer(); + for (auto i = 0u; i < num_ticks_passed; ++i) { + ++state::tick; // possibly after? + state::delta_ticks -= 1.0f; + localplayer.extrapolate(); + ++localplayer.get_mutable_latest_sequence(); + state::connection->usend_packet(make_move_packet(localplayer)); + } + + // reset all commands except for flying, more later probably + const auto retained = shared::animate::flying; + localplayer.get_mutable_commands() &= retained; +} + // requires SDL_MOUSEMOTION static void handle_mousemotion(const SDL_Event& event) noexcept { const float sens = settings::get<float>({"gameplay", "mouse_sensitivity"}, 0.0235f); - auto& lp = get_localplayer(::players); - auto& angles = lp.viewangles; + auto& lp = get_localplayer(); + auto& angles = lp.get_mutable_angles(); const float pitch_offset = static_cast<float>(event.motion.yrel) * sens; const float yaw_offset = static_cast<float>(event.motion.xrel) * sens; - angles.pitch = std::clamp(angles.pitch - glm::radians(pitch_offset), - glm::radians(-89.0f), glm::radians(89.0f)); - angles.yaw = - std::fmod(angles.yaw + glm::radians(yaw_offset), glm::radians(360.0f)) + - (angles.yaw < 0.0f) * glm::radians(360.0f); + angles.pitch -= glm::radians(pitch_offset); + angles.yaw += glm::radians(yaw_offset); + angles.normalise(); + angles.clamp(); } // requires SDL_MOUSEBUTTONDOWN @@ -395,37 +484,93 @@ static void handle_mousebuttons(const SDL_Event& event) noexcept { return; } + auto& lp = get_localplayer(); const auto mode = event.button.button == SDL_BUTTON_LEFT - ? client::movement::interact_mode::remove - : client::movement::interact_mode::add; - - const auto position = - client::movement::interact(get_localplayer(::players), mode, ::chunks); - + ? movement::interact_mode::remove + : movement::interact_mode::add; + const auto position = movement::interact(lp, mode, state::chunks); if (!position.has_value()) { return; } const auto& [chunk_pos, block_pos] = *position; + auto& block = state::chunks.find(chunk_pos)->second->get_block(block_pos); + + if (mode == movement::interact_mode::remove) { + lp.inventory.maybe_add(shared::item::block::get_type(block.type), 1, + client::item::make_item); + block.type = shared::world::block::type::air; + + state::connection->rsend_packet( + make_remove_block_packet(chunk_pos, block_pos)); + } else { + // Adding is more complicated, needs the active item. + const auto& active = lp.get_active_item(); + auto& item = lp.inventory.contents[active]; + if (item == nullptr) { + return; + } + + const auto block_ptr = dynamic_cast<const shared::item::block*>(&*item); + if (block_ptr == nullptr) { + return; + } + lp.inventory.decrement(active); + block.type = block_ptr->type; + + state::connection->rsend_packet( + make_add_block_packet(chunk_pos, block_pos, active)); + } + + regenerate_surrounding_chunks(chunk_pos); +} - auto& connection = *client::state.connection; - switch (mode) { - case client::movement::interact_mode::add: - connection.usend_packet(make_add_block_packet(chunk_pos, block_pos)); +static void handle_space(const SDL_Event& event) noexcept { + if (event.type == SDL_KEYUP) { + return; + } + auto& commands = get_localplayer().get_mutable_commands(); + commands |= shared::animate::mask::jump; + + if (event.key.repeat) { + return; + } + constexpr auto JUMP_FLY_DELAY = std::chrono::milliseconds(250); + + static std::optional<shared::time_point_t> prev = std::nullopt; + const auto now = std::chrono::steady_clock::now(); + if (prev.has_value() && now < *prev + JUMP_FLY_DELAY) { + commands ^= shared::animate::mask::flying; + prev.reset(); + return; + } + + prev.emplace(now); +} + +static void handle_keys(const SDL_Event& event) noexcept { + switch (event.key.keysym.sym) { + case SDLK_SPACE: + handle_space(event); break; - case client::movement::interact_mode::remove: - connection.usend_packet(make_remove_block_packet(chunk_pos, block_pos)); - ::last_block = ::chunks[chunk_pos]->get_block(block_pos); + default: break; } } static void handle_events(const SDL_Event& event) noexcept { - if (client::window::is_open()) { + if (window::is_open()) { + return; + } + if (!state::localplayer_index.has_value()) { return; } switch (event.type) { + case SDL_KEYDOWN: + case SDL_KEYUP: + handle_keys(event); + break; case SDL_MOUSEBUTTONDOWN: handle_mousebuttons(event); break; @@ -437,79 +582,124 @@ static void handle_events(const SDL_Event& event) noexcept { } } -static void authenticate_client(shared::net::connection& connection) noexcept { - const auto rand_alphanum_string = [](const int size) -> std::string { - static const std::string allowed = "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "1234567890"; - std::string ret; - for (int i = 0; i < size; ++i) { - std::ranges::sample(allowed, std::back_inserter(ret), 1, - std::random_device{}); - } +static std::string get_username() { + const settings::setting_pair_t loc = {"gameplay", "username"}; + if (const auto username = settings::maybe_get_setting_str(loc); + username.has_value()) { + return *username; + } - return ret; - }; + shared::print::notify << shared::print::time + << "first launch detected, username required\n"; + std::string ret; + while (ret.empty() || !std::ranges::all_of(ret, isalnum)) { + std::cin.clear(); + std::cout << "enter a valid username: "; + std::getline(std::cin, ret); + } + settings::set_setting_str(loc, ret); + return ret; +} - const std::string username = settings::get<std::string>( - {"auth", "username"}, - rand_alphanum_string(shared::MAX_USER_PASS_LENGTH)); - const std::string password = settings::get<std::string>( - {"auth", "password"}, - rand_alphanum_string(shared::MAX_USER_PASS_LENGTH)); +static std::string get_password(const std::string_view& address) { + const settings::setting_pair_t loc = { + "auth", std::string{address == "0.0.0.0" ? "localhost" : address}}; + if (const auto password = settings::maybe_get_setting_str(loc); + password.has_value()) { + return *password; + } - connection.rsend_packet(make_auth_packet(username, password)); + // We generate a random string as our password. Our password has decent + // complexitity but considering we don't use any encryption it is worthless + // to any real attacker and just basic authentication. + std::string ret; + std::random_device rand{}; + std::uniform_int_distribution<char> uniform{'a', 'z'}; + for (int i = 0; i < 256; ++i) { + const char random_char = uniform(rand); + ret.push_back(random_char); + } + settings::set_setting_str(loc, ret); + return ret; } -void main(const std::string_view address, const std::string_view port) { - client::state.address = address; - client::state.port = port; +static void send_auth_packet(const std::string_view& address, + shared::net::connection& connection) { + const std::string username = get_username(); + const std::string password = get_password(address); - shared::net::connection connection = make_connection(address, port); - shared::print::notify("client: connected to " + std::string{address} + ':' + - std::string{port} + '\n'); - client::state.connection = &connection; + connection.rsend_packet(make_auth_packet(username, password)); +} + +static bool should_do_loop(shared::net::connection& connection) noexcept { + if (shared::should_exit) { + return false; + } - client::render::init(); - client::input::register_event_handler(&handle_events); + if (!connection.good()) { + shared::print::notify << shared::print::time + << "client: disconnected for \"" + << connection.get_bad_reason() << "\"\n"; + shared::should_exit = true; // cleanup server if necessary + return false; + } - authenticate_client(connection); + return true; +} - while (!shared::should_exit) { - // Parse all new packets. - while (const auto packet = connection.recv_packet()) { - parse_packet(packet.value()); +static void do_client_loop(shared::net::connection& connection) { + while (should_do_loop(connection)) { + connection.poll(); + while (auto packet = connection.recv_packet()) { + parse_packet(std::move(*packet)); } - // Wait for localplayer to be constructed before doing anything. - if (::players.empty()) { + if (!state::has_initialised()) { continue; } - // Handle input, which may prime text input -> send it to the - // server. - update_input(); - // Send our localplayer to the server. - if (::should_send_move) { - send_move_packet(connection); - ::should_send_move = false; - } - update_chunks(connection); - update_state(); - update_players(); - - client::draw::draw(::players, ::chunks); + update_pre_move(); + maybe_send_move(); + update_post_move(); - if (!connection.good()) { - shared::print::notify("client: disconnected for \"" + - connection.get_bad_reason() + "\"\n"); - shared::should_exit = true; - } + render::draw(state::entities, state::chunks); } - shared::print::notify("client: disconnecting from server\n"); +} + +static void init_client(const std::string_view& address, + const std::string_view& port, + shared::net::connection& connection) { + // Setup client state, some vars should be cleaned up later (raii lol). + state::address = address; + state::port = port; + state::connection = &connection; + + input::register_event_handler(&handle_events); + send_auth_packet(address, connection); + + render::init(); +} + +static void cleanup_client() { + state::connection = nullptr; + state::localplayer_index.reset(); + state::chunks.clear(); + state::entities.clear(); + render::quit(); + settings::save(); +} + +void main(const std::string_view address, const std::string_view port) { + shared::net::connection connection = make_connection(address, port); + init_client(address, port, connection); + shared::print::notify << shared::print::time << "client: connected to " + << address << ':' << port << '\n'; + + do_client_loop(connection); - client::render::quit(); - client::settings::save(); + shared::print::notify << shared::print::time + << "client: disconnecting from server\n"; + cleanup_client(); } } // namespace client |
