#include "NetClient.h" #include "hd2d/Log.h" namespace bs { bool NetClient::connect(const std::string& host, uint16_t port, const std::string& token) { disconnect(); net::initialize(); token_ = token; error_.clear(); hasProfile_ = false; players.clear(); snapshots.clear(); events.clear(); dialogues.clear(); toasts.clear(); chat.clear(); welcome_ = {}; profile_ = {}; host_ = std::make_unique(); if (!host_->connect(host, port)) { state_ = State::Failed; error_ = "Could not resolve " + host; return false; } peer_ = 1; // first (only) peer of a client host state_ = State::Connecting; return true; } void NetClient::disconnect() { if (state_ != State::Idle && state_ != State::Failed && peer_) { host_->disconnect(peer_); host_->flush(); } state_ = State::Idle; peer_ = 0; } uint32_t NetClient::pingMs() const { return peer_ && host_ ? host_->roundTripMs(peer_) : 0; } void NetClient::send(net::Channel ch, const Writer& w) { if (peer_ && host_) host_->send(peer_, ch, w.data()); } void NetClient::update() { if (!host_ || !host_->valid()) return; net::Event ev; while (host_ && host_->poll(ev, 0)) { switch (ev.type) { case net::Event::Connect: { peer_ = ev.peer; Writer w(Msg::Hello); HelloMsg{kProtocolVersion, token_}.write(w); send(net::Reliable, w); break; } case net::Event::Disconnect: if (state_ != State::Failed) error_ = state_ == State::Connecting ? "Could not reach the server" : "Disconnected from the server"; state_ = State::Failed; peer_ = 0; break; case net::Event::Receive: handle(ev.data); break; default: break; } } if (host_) host_->flush(); } void NetClient::handle(const std::vector& data) { if (data.empty()) return; Reader r(data.data() + 1, data.size() - 1); switch (Msg(data[0])) { case Msg::Welcome: welcome_.read(r); if (!r.ok()) return; state_ = welcome_.hasCharacter ? State::InGame : State::NeedCharacter; break; case Msg::Reject: error_ = r.str(); state_ = State::Failed; break; case Msg::Profile: { ProfileMsg p; p.read(r); if (!r.ok()) return; profile_ = std::move(p); hasProfile_ = true; profileChanged = true; if (state_ == State::NeedCharacter) state_ = State::InGame; break; } case Msg::PlayerInfo: { PlayerInfoMsg p; p.read(r); if (r.ok()) players[p.entityId] = std::move(p); break; } case Msg::PlayerLeft: { uint32_t id = r.u32(); players.erase(id); playersLeft.push_back(id); break; } case Msg::Snapshot: { SnapshotMsg s; s.read(r); if (!r.ok()) return; snapshots.push_back(std::move(s)); while (snapshots.size() > 8) snapshots.pop_front(); break; } case Msg::Event: { GameEvent e; e.read(r); if (r.ok()) events.push_back(e); break; } case Msg::Dialogue: dialogues.push_back(r.str()); break; case Msg::ChatLine: { ChatEntry c; c.entity = r.u32(); c.name = r.str(); c.text = r.str(); if (r.ok()) chat.push_back(std::move(c)); if (chat.size() > 40) chat.erase(chat.begin()); break; } case Msg::Toast: toasts.push_back(r.str()); break; default: break; } } void NetClient::sendCreate(const std::string& name, const Appearance& look) { Writer w(Msg::CreateCharacter); CreateCharacterMsg{name, look}.write(w); send(net::Reliable, w); } void NetClient::sendInput(const InputCmd& cmd) { recentInputs_.push_back(cmd); if (recentInputs_.size() > 4) recentInputs_.erase(recentInputs_.begin()); Writer w(Msg::Input); InputMsg m; m.cmds = recentInputs_; m.write(w); send(net::Unreliable, w); } void NetClient::sendAction(ActionKind kind, uint32_t target, float x, float z) { Writer w(Msg::Action); ActionMsg{kind, target, x, z}.write(w); send(net::Reliable, w); } void NetClient::sendChat(const std::string& text) { Writer w(Msg::Chat); w.str(text); send(net::Reliable, w); } void NetClient::sendDialogueDone(const std::string& script) { Writer w(Msg::DialogueDone); w.str(script); send(net::Reliable, w); } } // namespace bs