aboutsummaryrefslogtreecommitdiff
path: root/src/client/client.cc
diff options
context:
space:
mode:
authorNicolas James <Eele1Ephe7uZahRie@tutanota.com>2025-02-12 21:57:46 +1100
committerNicolas James <Eele1Ephe7uZahRie@tutanota.com>2025-02-12 21:57:46 +1100
commite4483eca01b48b943cd0461e24a74ae1a3139ed4 (patch)
treeed58c3c246e3af1af337697695d780aa31f6ad9a /src/client/client.cc
parent1cc08c51eb4b0f95c30c0a98ad1fc5ad3459b2df (diff)
Update to most recent version (old initial commit)
Diffstat (limited to 'src/client/client.cc')
-rw-r--r--src/client/client.cc690
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