#include "world.hpp" #include #include #include "assets/cache.hpp" #include "collision/object_info.hpp" #include "destroyed_object.hpp" #include "utils/allocnum.hpp" #include "player_character.hpp" game::World::World(std::string mapname) : Scheduler(time_ms_), map_(*this, std::move(mapname)) {} void game::World::SendInitData(Player& player, net::OutMessage& msg) { msg.Write(net::MapName(map_.GetName())); msg.Write(destroyed_objs_.size()); for (auto objnum : destroyed_objs_) { msg.Write(objnum); } } net::EntNum game::World::GetNewEntnum() { auto entnum = utils::AllocNum(ents_, last_entnum_); if (!entnum) throw std::runtime_error("Max entities reached"); return entnum; } void game::World::RegisterEntity(std::unique_ptr ent) { auto& entslot = ents_[ent->GetEntNum()]; if (entslot) throw std::runtime_error("Attempted to register entity with an occupied entnum"); entslot = std::move(ent); } void game::World::Update(int64_t delta_time) { time_ms_ += delta_time; float delta_s = static_cast(delta_time) * 0.001f; // GetBtWorld().stepSimulation(delta_s, 1, delta_s); GetBtWorld().stepSimulation(delta_s, 2, delta_s * 0.5f); HandleContacts(); RunTasks(); // update entities for (auto it = ents_.begin(); it != ents_.end();) { it->second->TryUpdate(); if (it->second->IsRemoved()) it = ents_.erase(it); else ++it; } } void game::World::FinishFrame() { ResetMsg(); // reset ent msgs for (auto& [entnum, ent] : ents_) { ent->FinalizeFrame(); } } void game::World::DestructibleDestroyed(net::ObjNum num, std::unique_ptr col) { auto& destroyed_obj = Spawn(std::move(col)); Schedule(120000, [this, num] { RespawnObj(num); }); } game::Entity* game::World::GetEntity(net::EntNum entnum) { auto it = ents_.find(entnum); if (it == ents_.end()) return nullptr; return it->second.get(); } void game::World::RespawnObj(net::ObjNum objnum) { if (destroyed_objs_.erase(objnum) > 0) { map_.SpawnObj(objnum); SendObjRespawnedMsg(objnum); } } struct UseTargetAabbCallback : public btBroadphaseAabbCallback { game::PlayerCharacter& character; glm::vec3 pos; const game::UseTarget* best_target = nullptr; float best_dist = std::numeric_limits::max(); game::UseTargetQueryResult& best_res; float radius = 2.0f; UseTargetAabbCallback(game::PlayerCharacter& character, game::UseTargetQueryResult& res) : character(character), pos(character.GetRoot().GetGlobalPosition()), best_res(res) { auto vehicle = character.GetVehicle(); if (vehicle) { radius = 5.0f; pos = vehicle->GetRoot().GetGlobalPosition(); } } virtual bool process(const btBroadphaseProxy* proxy) { auto obj = reinterpret_cast(proxy->m_clientObject); collision::ObjectType type; collision::ObjectFlags flags; collision::ObjectCallback* obj_cb; collision::GetObjectInfo(obj, type, flags, obj_cb); if ((flags & collision::OF_USABLE) == 0 || !obj_cb) return true; auto usable = dynamic_cast(obj_cb); if (!usable) return true; auto& matrix = usable->GetWSTransformMatrix(); for (const auto& target : usable->GetUseTargets()) { glm::vec3 pos_world = matrix * glm::vec4(target.position, 1.0f); float dist = glm::distance(pos, pos_world); if (dist < radius && dist < best_dist) { game::UseTargetQueryResult res{}; if (!usable->QueryUseTarget(character, target.id, res)) continue; best_res = res; best_dist = dist; best_target = ⌖ } } return true; } }; const game::UseTarget* game::World::GetBestUseTarget(game::PlayerCharacter& character, game::UseTargetQueryResult& res) { const float radius = 5.0f; UseTargetAabbCallback cb(character, res); btVector3 min(cb.pos.x - radius, cb.pos.y - radius, cb.pos.z - radius); btVector3 max(cb.pos.x + radius, cb.pos.y + radius, cb.pos.z + radius); GetBtBroadphase().aabbTest(min, max, cb); return cb.best_target; } void game::World::HandleContacts() { auto& bt_world = GetBtWorld(); int numManifolds = bt_world.getDispatcher()->getNumManifolds(); // destructibles static std::vector to_destroy; to_destroy.clear(); auto ProcessContact = [&](btRigidBody* body, btRigidBody* other_body, const btVector3& pos, const btVector3& normal, float impulse) { collision::ObjectType type; collision::ObjectFlags flags; collision::ObjectCallback* cb; collision::GetObjectInfo(body, type, flags, cb); if (cb && (flags & collision::OF_NOTIFY_CONTACT)) { collision::ContactInfo info; info.pos = glm::vec3(pos.x(), pos.y(), pos.z()); info.normal = glm::vec3(normal.x(), normal.y(), normal.z()); info.impulse = impulse; cb->OnContact(info); } if (type == collision::OT_MAP_OBJECT && (flags & collision::OF_DESTRUCTIBLE)) { auto col = dynamic_cast(cb); if (!col) return; if (impulse > col->GetDestroyThreshold()) { to_destroy.push_back(col->GetNum()); other_body->applyCentralImpulse(-normal * impulse * 0.5f); } return; } }; // std::cout << "Checking " << numManifolds << " manifolds for destructible collisions..." << std::endl; for (int i = 0; i < numManifolds; i++) { btPersistentManifold* contactManifold = bt_world.getDispatcher()->getManifoldByIndexInternal(i); btRigidBody* body0 = const_cast(static_cast(contactManifold->getBody0())); btRigidBody* body1 = const_cast(static_cast(contactManifold->getBody1())); for (int j = 0; j < contactManifold->getNumContacts(); j++) { btManifoldPoint& pt = contactManifold->getContactPoint(j); ProcessContact(body0, body1, pt.m_localPointA, -pt.m_normalWorldOnB, pt.getAppliedImpulse()); ProcessContact(body1, body0, pt.m_localPointB, pt.m_normalWorldOnB, pt.getAppliedImpulse()); } } // destroy objs outside the loop to avoid corruption of the manifold list for (auto objnum : to_destroy) { DestroyObject(objnum); } } void game::World::DestroyObject(net::ObjNum objnum) { SendObjDestroyedMsg(objnum); destroyed_objs_.insert(objnum); auto col = map_.DestroyObj(objnum); if (col) { DestructibleDestroyed(objnum, std::move(col)); } } void game::World::SendObjDestroyedMsg(net::ObjNum objnum) { auto msg = BeginMsg(net::MSG_OBJDESTROY); msg.Write(objnum); } void game::World::SendObjRespawnedMsg(net::ObjNum objnum) { auto msg = BeginMsg(net::MSG_OBJRESPAWN); msg.Write(objnum); }