#include "worldview.hpp" #include "assets/cache.hpp" #include "simple_entity_view.hpp" #include "characterview.hpp" #include "vehicleview.hpp" #include "markerview.hpp" #include "client_session.hpp" #include "draw_args.hpp" #include "net/utils.hpp" game::view::WorldView::WorldView(ClientSession& session, net::InMessage& msg) : session_(session), audiomaster_(session_.GetAudioMaster()) { net::MapName mapname; if (!msg.Read(mapname)) throw EntityInitError(); map_ = std::make_unique(*this, std::string(mapname)); // init destroyed objs net::ObjCount objcount; if (!msg.Read(objcount)) throw EntityInitError(); for (net::ObjCount i = 0; i < objcount; ++i) { net::ObjNum objnum; if (!msg.Read(objnum)) throw EntityInitError(); map_->EnableObj(objnum, false); } // cache common snds and stuff Cache(assets::CacheManager::GetSound("data/breaksign.snd")); Cache(assets::CacheManager::GetSound("data/breakpatnik.snd")); Cache(assets::CacheManager::GetSound("data/breakwindow.snd")); Cache(assets::CacheManager::GetSound("data/breakwood.snd")); Cache(assets::CacheManager::GetSound("data/cardoor.snd")); Cache(assets::CacheManager::GetSound("data/crash.snd")); env_ = std::make_unique(); env_->SetDayTime(12.0f); } bool game::view::WorldView::ProcessMsg(net::MessageType type, net::InMessage& msg) { switch (type) { case net::MSG_ENV: return ProcessEnvMsg(msg); case net::MSG_ENTSPAWN: return ProcessEntSpawnMsg(msg); case net::MSG_ENTMSG: return ProcessEntMsgMsg(msg); case net::MSG_UPDATEENTS: return ProcessUpdateEntsMsg(msg); case net::MSG_ENTDESTROY: return ProcessEntDestroyMsg(msg); case net::MSG_OBJDESTROY: return ProcessObjDestroyOrRespawnMsg(msg, false); case net::MSG_OBJRESPAWN: return ProcessObjDestroyOrRespawnMsg(msg, true); case net::MSG_BEAM: return ProcessBeamMsg(msg); default: return false; } } void game::view::WorldView::Update(const UpdateInfo& info) { time_ = info.time; if (!map_->IsLoaded()) map_->LoadNext(); for (const auto& [entnum, ent] : ents_) { ent->TryUpdate(info); } UpdateEnv(); UpdateBeams(); } void game::view::WorldView::Draw(const DrawArgs& args) const { if (!map_->IsLoaded()) { DrawLoadingScreen(args); return; } DrawEnv(args); map_->Draw(args); for (const auto& [entnum, ent] : ents_) { if (args.frustum.IsSphereVisible(ent->GetBoundingSphere())) ent->Draw(args); } DrawBeams(args); } game::view::EntityView* game::view::WorldView::GetEntity(net::EntNum entnum) { auto it = ents_.find(entnum); if (it != ents_.end()) return it->second.get(); return nullptr; } void game::view::WorldView::DrawLoadingScreen(const DrawArgs& args) const { float margin = 50.0f; glm::vec2 size(400.0f, 15.0f); glm::vec2 pos(margin, args.screen_size.y - margin - size.y); int loaded_percent = map_->GetLoadingPercent(); float loaded = static_cast(loaded_percent) * 0.01f; args.gui.DrawRect(pos, pos + size, 0x77FFFFFF); args.gui.DrawRect(pos, pos + glm::vec2(size.x * loaded, size.y), 0xFF00FFFF); std::string load_text = std::to_string(loaded_percent) + "%"; args.gui.DrawTextAligned(load_text, pos + glm::vec2(size.x + 50.0f, size.y * 0.5f), glm::vec2(-0.5f, -0.5f)); } void game::view::WorldView::UpdateEnv() { if (!env_) return; env_->SetDayTime(glm::mix(daytime0_, daytime1_, glm::clamp(GetTime() - env_msg_time_, 0.0f, 1.0f))); } void game::view::WorldView::DrawEnv(const DrawArgs& args) const { if (env_) { env_->Draw(args); return; } // args.env.clear_color = glm::vec3(0.5f, 0.7f, 1.0f); args.env.clear_color = glm::vec3(0.1f, 0.15f, 0.3f); args.env.ambient_light = glm::vec3(0.4f, 0.4f, 0.4f); args.env.sun_color = glm::vec3(0.5f, 0.8f, 1.0f) * 0.4f; args.env.sun_direction = glm::normalize(glm::vec3(1.0f, 1.0f, -1.0f)); } bool game::view::WorldView::ProcessEnvMsg(net::InMessage& msg) { float new_daytime; if (!msg.Read(new_daytime)) return false; if (!env_) return true; env_msg_time_ = GetTime(); daytime0_ = env_->GetDayTime(); daytime1_ = new_daytime; if (daytime1_ < daytime0_) daytime0_ -= 24.0f; return true; } bool game::view::WorldView::ProcessEntSpawnMsg(net::InMessage& msg) { net::EntNum entnum; net::EntType type; if (!msg.Read(entnum) || !msg.Read(type)) return false; auto& entslot = ents_[entnum]; if (entslot) entslot.reset(); try { switch (type) { case net::ET_SIMPLE: entslot = std::make_unique(*this, msg); break; case net::ET_CHARACTER: entslot = std::make_unique(*this, msg); break; case net::ET_VEHICLE: entslot = std::make_unique(*this, msg); break; case net::ET_MARKER: entslot = std::make_unique(*this, msg); break; default: ents_.erase(entnum); return false; // unknown type } return true; } catch (const EntityInitError& e) // failed { ents_.erase(entnum); return false; } } bool game::view::WorldView::ProcessEntMsgMsg(net::InMessage& msg) { net::EntNum entnum; net::EntMsgType type; if (!msg.Read(entnum) || !msg.Read(type)) return false; auto ent_it = ents_.find(entnum); if (ent_it == ents_.end()) return false; return ent_it->second->ProcessMsg(type, msg); } bool game::view::WorldView::ProcessUpdateEntsMsg(net::InMessage& msg) { net::EntCount count; if (!msg.Read(count)) return false; if (count > ents_.size()) return false; // wanna update more than we have net::EntNum current = 0; auto it = ents_.begin(); for (net::EntNum i = 0; i < count; ++i) { int64_t diff; if (!msg.ReadVarInt(diff)) return false; current += static_cast(diff); while (it->first < current) { it->second->ProcessUpdateMsg(nullptr); // blank update ++it; if (it == ents_.end()) return false; } if (it->first != current) { return false; // exact num wasnt found and it overrun } if (!it->second->ProcessUpdateMsg(&msg)) return false; } // process remaining for (; it != ents_.end(); ++it) { it->second->ProcessUpdateMsg(nullptr); } return true; } bool game::view::WorldView::ProcessEntDestroyMsg(net::InMessage& msg) { net::EntNum entnum; if (!msg.Read(entnum)) return false; ents_.erase(entnum); return true; } bool game::view::WorldView::ProcessObjDestroyOrRespawnMsg(net::InMessage& msg, bool enable) { net::ObjNum objnum; if (!msg.Read(objnum)) return false; map_->EnableObj(objnum, enable); return true; } bool game::view::WorldView::ProcessBeamMsg(net::InMessage& msg) { BeamView beam; if (!net::ReadPosition(msg, beam.start) || !net::ReadPosition(msg, beam.end) || !net::ReadRGB(msg, beam.color) || !msg.Read(beam.expiration)) return false; beam.expiration += GetTime(); beam.width = 0.02f; beams_.emplace_back(beam); return true; } void game::view::WorldView::Cache(std::any val) { cache_.emplace_back(std::move(val)); } void game::view::WorldView::UpdateBeams() { beams_.erase(std::remove_if(beams_.begin(), beams_.end(), [this](const BeamView& beam) { return beam.expiration <= GetTime(); }), beams_.end()); } void game::view::WorldView::DrawBeams(const DrawArgs& args) const { for (const auto& beam : beams_) { args.dlist.AddBeam(beam.start, beam.end, beam.color | 0xFF000000, beam.width); } }