#include "client_session.hpp" #include "client/app.hpp" #include // #include #include "utils/version.hpp" #include "utils.hpp" #include "vehicleview.hpp" #include "assets/cache.hpp" #include "game/player_hud_data.hpp" game::view::ClientSession::ClientSession(App& app) : app_(app), hud_(app.GetTime()) { crosshair_texture_ = assets::CacheManager::GetTexture("data/crosshair.png"); // send login auto msg = BeginMsg(net::MSG_ID); msg.Write(FEKAL_VERSION); msg.Write(net::PlayerName(app.GetUserName())); } bool game::view::ClientSession::ProcessMessage(net::InMessage& msg) { while (true) { net::MessageType type = net::MSG_NONE; if (!msg.Read(type)) return true; if (type == net::MSG_NONE || type >= net::MSG_COUNT) return false; if (!ProcessSingleMessage(type, msg)) return false; } } bool game::view::ClientSession::ProcessSingleMessage(net::MessageType type, net::InMessage& msg) { MSGDEBUG(std::cout << "[MSG] received " << (uint32_t)type << std::endl;) switch (type) { case net::MSG_CHWORLD: return ProcessWorldMsg(msg); case net::MSG_CAM: return ProcessCameraMsg(msg); case net::MSG_CHAT: return ProcessChatMsg(msg); case net::MSG_HUD: return ProcessHudMsg(msg); case net::MSG_USETARGET: return ProcessUseTargetMsg(msg); case net::MSG_REMOTEMENU: return ProcessMenuMsg(msg); default: // try pass the msg to world if (world_ && world_->ProcessMsg(type, msg)) return true; return false; } } void game::view::ClientSession::Input(game::PlayerInputType in, bool pressed, bool repeated) { if (pressed && ProcessMenuInput(in)) return; if (repeated) return; SendInput(in, pressed); } void game::view::ClientSession::ProcessMouseMove(float delta_yaw, float delta_pitch) { auto sens_mult = glm::mix(1.0f, 0.3f, camera_controller_.GetAimFactor()); float yaw = glm::mod(camera_controller_.GetYaw() + delta_yaw * sens_mult, glm::two_pi()); float pitch = camera_controller_.GetPitch() + delta_pitch * sens_mult; pitch = glm::clamp(pitch, glm::radians(-89.0f), glm::radians(89.0f)); // Clamp pitch to avoid gimbal lock camera_controller_.SetViewAngles(yaw, pitch); } void game::view::ClientSession::Update(const UpdateInfo& info) { if (world_) { world_->Update(info); SendViewAngles(info.time); UpdateCamera(info); } } void game::view::ClientSession::Draw(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui) { if (world_) { DrawWorld(dlist, params, gui); if (world_->IsLoaded()) { DrawCrosshair(gui); hud_.Draw(gui); } } DrawMenus(gui); } audio::Master& game::view::ClientSession::GetAudioMaster() const { return app_.GetAudioMaster(); } bool game::view::ClientSession::ProcessWorldMsg(net::InMessage& msg) { try { world_ = std::make_unique(*this, msg); } catch (const EntityInitError&) { return false; } return true; } bool game::view::ClientSession::ProcessCameraMsg(net::InMessage& msg) { if (!msg.Read(camera_info_.character_entnum) || !msg.Read(camera_info_.rideable_entnum) || !msg.Read(camera_info_.flags)) return false; return true; } bool game::view::ClientSession::ProcessChatMsg(net::InMessage& msg) { net::ChatMessage chatm; if (!msg.Read(chatm)) return false; app_.AddChatMessagePrefix("Server", chatm); return true; } bool game::view::ClientSession::ProcessHudMsg(net::InMessage& msg) { PlayerHudFields fields{}; if (!msg.Read(fields)) return false; PlayerHudData hud_data{}; if (fields & PHUD_HEALTH) { if (!msg.Read(hud_data.health)) return false; hud_.SetHealth(static_cast(hud_data.health)); } if (fields & PHUD_WEAPON_SLOTS) { if (!msg.Read(hud_data.weapon_slots)) return false; hud_.SetWeaponSlots(hud_data.weapon_slots); } if (fields & PHUD_ITEM) { net::ModelName item_name; if (!msg.Read(item_name)) return false; hud_data.held_item = item_name; // determine clip size size_t clip_size = 0; size_t item_slot = 0; if (!hud_data.held_item.empty()) { auto item = assets::CacheManager::GetItem("data/" + hud_data.held_item + ".item"); clip_size = item->clip_size; item_slot = item->slot; } hud_.SetItemInfo(hud_data.held_item, item_slot, clip_size); } if (fields & PHUD_AMMO_LOADED) { if (!msg.Read(hud_data.ammo_loaded)) return false; hud_.SetLoadedAmmo(static_cast(hud_data.ammo_loaded)); } if (fields & PHUD_AMMO_TOTAL) { if (!msg.Read(hud_data.ammo_total)) return false; hud_.SetTotalAmmo(static_cast(hud_data.ammo_total)); } return true; } bool game::view::ClientSession::ProcessUseTargetMsg(net::InMessage& msg) { net::UseTargetName text, error_text; float delay; if (!msg.Read(text) || !msg.Read(error_text) || !msg.Read(delay)) return false; hud_.SetUseTargetData(text, error_text, delay); return true; } bool game::view::ClientSession::ProcessMenuMsg(net::InMessage& msg) { net::MenuId id; net::MenuMessageType type; if (!msg.Read(id) || !msg.Read(type)) return false; switch (type) { case net::MMSG_CREATE: { if (FindMenu(id)) return false; remote_menus_.push_back(std::make_unique(*this, id)); return true; } case net::MMSG_CLOSE: { std::erase_if(remote_menus_, [id](auto& menu) { return menu->GetId() == id; }); return true; } default: { auto menu = FindMenu(id); if (!menu) return false; return menu->ProcessMessage(type, msg); } } } void game::view::ClientSession::UpdateCamera(const UpdateInfo& info) { auto character = world_->GetEntity(camera_info_.character_entnum); camera_controller_.SetCharacterTransform(character ? &character->GetRoot().matrix : nullptr); auto rideable = world_->GetEntity(camera_info_.rideable_entnum); camera_controller_.SetRideableTransform(rideable ? &rideable->GetRoot().matrix : nullptr); camera_controller_.SetAiming(camera_info_.flags & CAM_AIMING); camera_controller_.Update(info.delta_time); camera_controller_.Recalculate(world_.get()); } void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui) { // glm::mat4 view = glm::lookAt(glm::vec3(15.0f, 0.0f, 1.0f), glm::vec3(0.0f, 0.0f, -13.0f), glm::vec3(0.0f, // 0.0f, 1.0f)); float aspect = static_cast(params.screen_width) / static_cast(params.screen_height); const float farplane = 3000.0f; glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, farplane); glm::mat4 view = camera_controller_.GetViewMatrix(); params.view_proj = proj * view; params.cam_pos = camera_controller_.GetEye(); // glm::mat4 fake_view_proj = glm::perspective(glm::radians(30.0f), aspect, 0.1f, 3000.0f) * view; game::view::DrawArgs draw_args(dlist, params.env, gui, params.view_proj, params.cam_pos, glm::ivec2(params.screen_width, params.screen_height), farplane, 500.0f); world_->Draw(draw_args); glm::mat4 camera_world = glm::inverse(view); GetAudioMaster().SetListenerOrientation(camera_world); } void game::view::ClientSession::SendInput(game::PlayerInputType type, bool enable) { auto msg = BeginMsg(net::MSG_IN); uint8_t val = type; if (enable) val |= 128; msg.Write(val); } void game::view::ClientSession::SendViewAngles(float time) { if (time - last_send_time_ < 0.040f) return; net::ViewYawQ yaw_q; net::ViewPitchQ pitch_q; yaw_q.Encode(camera_controller_.GetYaw()); pitch_q.Encode(camera_controller_.GetPitch()); if (yaw_q.value == view_yaw_q_.value && pitch_q.value == view_pitch_q_.value) return; auto msg = BeginMsg(net::MSG_VIEWANGLES); msg.Write(yaw_q.value); msg.Write(pitch_q.value); view_yaw_q_.value = yaw_q.value; view_pitch_q_.value = pitch_q.value; last_send_time_ = time; } void game::view::ClientSession::DrawMenus(gui::Context& gui) const { if (remote_menus_.empty()) return; auto& top_menu = *remote_menus_.back(); top_menu.Draw(gui, glm::vec2(20.0f, 100.0f)); } bool game::view::ClientSession::ProcessMenuInput(game::PlayerInputType in) { if (remote_menus_.empty()) return false; gui::MenuInput mi; if (!InputToMenuInput(in, mi)) return false; remote_menus_.back()->Input(mi); return true; } game::view::RemoteMenuView* game::view::ClientSession::FindMenu(net::MenuId id) const { for (auto& menu : remote_menus_) { if (menu->GetId() == id) return menu.get(); } return nullptr; } void game::view::ClientSession::DrawCrosshair(gui::Context& gui) const { if (camera_controller_.GetAimFactor() < 0.5f) return; // no aiming no crosshair const float crosshair_size = 32.0f; auto& viewport_size = gui.GetViewportSize(); auto p0 = viewport_size * 0.5f - crosshair_size * 0.5f; auto p1 = p0 + crosshair_size; gui.DrawRect(p0, p1, 0xFFFFFFFF, crosshair_texture_.get()); }