1.1.
This commit is contained in:
parent
5c844cf4d3
commit
370a2f60d0
118
CMakeLists.txt
118
CMakeLists.txt
@ -26,19 +26,13 @@ set(SOURCES
|
|||||||
"src/assets/skeleton.hpp"
|
"src/assets/skeleton.hpp"
|
||||||
"src/assets/skeleton.cpp"
|
"src/assets/skeleton.cpp"
|
||||||
"src/collision/trianglemesh.cpp"
|
"src/collision/trianglemesh.cpp"
|
||||||
# "src/game/entity.hpp"
|
|
||||||
# "src/game/entity.cpp"
|
|
||||||
# "src/game/meshinstance.hpp"
|
|
||||||
# "src/game/meshinstance.cpp"
|
|
||||||
"src/game/player_input.hpp"
|
"src/game/player_input.hpp"
|
||||||
# "src/game/player.hpp"
|
|
||||||
# "src/game/player.cpp"
|
|
||||||
"src/game/transform_node.hpp"
|
"src/game/transform_node.hpp"
|
||||||
# "src/game/world.hpp"
|
|
||||||
# "src/game/world.cpp"
|
|
||||||
"src/gameview/client_session.hpp"
|
"src/gameview/client_session.hpp"
|
||||||
|
"src/gameview/client_session.cpp"
|
||||||
"src/gameview/entityview.hpp"
|
"src/gameview/entityview.hpp"
|
||||||
"src/gameview/worldview.hpp"
|
"src/gameview/worldview.hpp"
|
||||||
|
"src/gameview/worldview.cpp"
|
||||||
"src/gfx/buffer_object.cpp"
|
"src/gfx/buffer_object.cpp"
|
||||||
"src/gfx/buffer_object.hpp"
|
"src/gfx/buffer_object.hpp"
|
||||||
"src/gfx/draw_list.hpp"
|
"src/gfx/draw_list.hpp"
|
||||||
@ -58,8 +52,11 @@ set(SOURCES
|
|||||||
"src/net/defs.hpp"
|
"src/net/defs.hpp"
|
||||||
"src/net/fixed_str.hpp"
|
"src/net/fixed_str.hpp"
|
||||||
"src/net/inmessage.hpp"
|
"src/net/inmessage.hpp"
|
||||||
|
"src/net/msg_producer.hpp"
|
||||||
|
"src/net/msg_producer.cpp"
|
||||||
"src/net/outmessage.hpp"
|
"src/net/outmessage.hpp"
|
||||||
"src/net/quantized.hpp"
|
"src/net/quantized.hpp"
|
||||||
|
"src/utils/defs.hpp"
|
||||||
"src/utils/files.hpp"
|
"src/utils/files.hpp"
|
||||||
"src/utils/files.cpp"
|
"src/utils/files.cpp"
|
||||||
"src/utils/transform.hpp"
|
"src/utils/transform.hpp"
|
||||||
@ -79,9 +76,23 @@ endif()
|
|||||||
|
|
||||||
|
|
||||||
# Include directories
|
# Include directories
|
||||||
target_include_directories(${MAIN_NAME} PRIVATE
|
target_include_directories(${MAIN_NAME} PRIVATE "src")
|
||||||
"src"
|
|
||||||
)
|
add_subdirectory(external/glm)
|
||||||
|
target_link_libraries(${MAIN_NAME} PRIVATE glm)
|
||||||
|
target_include_directories(${MAIN_NAME} PRIVATE "external/stb")
|
||||||
|
|
||||||
|
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||||
|
set(BUILD_BULLET2_DEMOS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(BUILD_UNIT_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(BUILD_PYBULLET OFF CACHE BOOL "" FORCE)
|
||||||
|
set(BUILD_OPENGL3_DEMOS OFF CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
|
add_subdirectory(external/bullet3)
|
||||||
|
target_link_libraries(${MAIN_NAME} PRIVATE BulletCollision BulletDynamics LinearMath Bullet3Common)
|
||||||
|
target_include_directories(${MAIN_NAME} PRIVATE "external/bullet3/src")
|
||||||
|
|
||||||
|
target_compile_definitions(${MAIN_NAME} PRIVATE CLIENT)
|
||||||
|
|
||||||
# Platform-specific SDL2 handling
|
# Platform-specific SDL2 handling
|
||||||
if (CMAKE_SYSTEM_NAME STREQUAL Emscripten)
|
if (CMAKE_SYSTEM_NAME STREQUAL Emscripten)
|
||||||
@ -134,18 +145,75 @@ endif()
|
|||||||
add_subdirectory(external/glad)
|
add_subdirectory(external/glad)
|
||||||
target_link_libraries(${MAIN_NAME} PRIVATE glad)
|
target_link_libraries(${MAIN_NAME} PRIVATE glad)
|
||||||
|
|
||||||
|
# add easywsclient
|
||||||
|
set(EASYWSCLIENT_PATH "external/easywsclient")
|
||||||
|
|
||||||
|
add_library(easywsclient STATIC
|
||||||
|
${EASYWSCLIENT_PATH}/easywsclient.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(easywsclient PUBLIC ${EASYWSCLIENT_PATH})
|
||||||
|
|
||||||
|
target_link_libraries(${MAIN_NAME} PRIVATE easywsclient)
|
||||||
|
|
||||||
|
# build server
|
||||||
|
set(ASIO_INCLUDE_DIR "external/asio/include")
|
||||||
|
include_directories(${ASIO_INCLUDE_DIR})
|
||||||
|
|
||||||
|
add_subdirectory(external/Crow)
|
||||||
|
set(SERVER_SOURCES
|
||||||
|
"src/assets/animation.hpp"
|
||||||
|
"src/assets/animation.cpp"
|
||||||
|
"src/assets/cache.hpp"
|
||||||
|
"src/assets/cache.cpp"
|
||||||
|
"src/assets/cmdfile.hpp"
|
||||||
|
"src/assets/cmdfile.cpp"
|
||||||
|
"src/assets/map.hpp"
|
||||||
|
"src/assets/map.cpp"
|
||||||
|
"src/assets/model.hpp"
|
||||||
|
"src/assets/model.cpp"
|
||||||
|
"src/assets/mesh_builder.cpp"
|
||||||
|
"src/assets/skeleton.hpp"
|
||||||
|
"src/assets/skeleton.cpp"
|
||||||
|
"src/collision/aabb.hpp"
|
||||||
|
"src/collision/dynamicsworld.hpp"
|
||||||
|
"src/collision/dynamicsworld.cpp"
|
||||||
|
"src/collision/trianglemesh.hpp"
|
||||||
|
"src/collision/trianglemesh.cpp"
|
||||||
|
"src/game/entity.hpp"
|
||||||
|
"src/game/entity.cpp"
|
||||||
|
"src/game/game.hpp"
|
||||||
|
"src/game/game.cpp"
|
||||||
|
"src/game/player_input.hpp"
|
||||||
|
"src/game/player.hpp"
|
||||||
|
"src/game/player.cpp"
|
||||||
|
"src/game/transform_node.hpp"
|
||||||
|
"src/game/world.hpp"
|
||||||
|
"src/game/world.cpp"
|
||||||
|
"src/net/defs.hpp"
|
||||||
|
"src/net/fixed_str.hpp"
|
||||||
|
"src/net/inmessage.hpp"
|
||||||
|
"src/net/msg_producer.hpp"
|
||||||
|
"src/net/msg_producer.cpp"
|
||||||
|
"src/net/outmessage.hpp"
|
||||||
|
"src/net/quantized.hpp"
|
||||||
|
"src/server/client.hpp"
|
||||||
|
"src/server/client.cpp"
|
||||||
|
"src/server/main.cpp"
|
||||||
|
"src/server/server.hpp"
|
||||||
|
"src/server/server.cpp"
|
||||||
|
"src/server/wsserver.hpp"
|
||||||
|
"src/server/wsserver.cpp"
|
||||||
|
"src/utils/defs.hpp"
|
||||||
|
"src/utils/files.hpp"
|
||||||
|
"src/utils/files.cpp"
|
||||||
|
"src/utils/transform.hpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SERVER_NAME server)
|
||||||
|
add_executable(${SERVER_NAME} ${SERVER_SOURCES})
|
||||||
|
target_link_libraries(${SERVER_NAME} PRIVATE Crow glm BulletCollision BulletDynamics LinearMath Bullet3Common)
|
||||||
|
target_include_directories(${SERVER_NAME} PRIVATE "src" "external/bullet3/src")
|
||||||
|
target_compile_definitions(${SERVER_NAME} PRIVATE SERVER)
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory(external/glm)
|
|
||||||
target_link_libraries(${MAIN_NAME} PRIVATE glm)
|
|
||||||
target_include_directories(${MAIN_NAME} PRIVATE "external/stb")
|
|
||||||
|
|
||||||
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
|
||||||
set(BUILD_BULLET2_DEMOS OFF CACHE BOOL "" FORCE)
|
|
||||||
set(BUILD_UNIT_TESTS OFF CACHE BOOL "" FORCE)
|
|
||||||
set(BUILD_PYBULLET OFF CACHE BOOL "" FORCE)
|
|
||||||
set(BUILD_OPENGL3_DEMOS OFF CACHE BOOL "" FORCE)
|
|
||||||
|
|
||||||
add_subdirectory(external/bullet3)
|
|
||||||
target_link_libraries(${MAIN_NAME} PRIVATE BulletCollision LinearMath Bullet3Common)
|
|
||||||
target_include_directories(${MAIN_NAME} PRIVATE "external/bullet3/src")
|
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
#include "cache.hpp"
|
#include "cache.hpp"
|
||||||
|
|
||||||
assets::TextureCache assets::CacheManager::texture_cache_;
|
|
||||||
assets::SkeletonCache assets::CacheManager::skeleton_cache_;
|
assets::SkeletonCache assets::CacheManager::skeleton_cache_;
|
||||||
assets::ModelCache assets::CacheManager::model_cache_;
|
assets::ModelCache assets::CacheManager::model_cache_;
|
||||||
|
assets::MapCache assets::CacheManager::map_cache_;
|
||||||
|
assets::VehicleCache assets::CacheManager::vehicle_cache_;
|
||||||
|
|
||||||
|
CLIENT_ONLY(assets::TextureCache assets::CacheManager::texture_cache_;)
|
||||||
@ -1,8 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "map.hpp"
|
||||||
#include "model.hpp"
|
#include "model.hpp"
|
||||||
#include "skeleton.hpp"
|
#include "skeleton.hpp"
|
||||||
|
#include "vehiclemdl.hpp"
|
||||||
|
|
||||||
|
#include "utils/defs.hpp"
|
||||||
|
|
||||||
|
#ifdef CLIENT
|
||||||
#include "gfx/texture.hpp"
|
#include "gfx/texture.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace assets
|
namespace assets
|
||||||
{
|
{
|
||||||
@ -36,11 +43,13 @@ private:
|
|||||||
std::map<std::string, std::weak_ptr<const T>> cache_;
|
std::map<std::string, std::weak_ptr<const T>> cache_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef CLIENT
|
||||||
class TextureCache final : public Cache<gfx::Texture>
|
class TextureCache final : public Cache<gfx::Texture>
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
PtrType Load(const std::string& key) override { return gfx::Texture::LoadFromFile(key); }
|
PtrType Load(const std::string& key) override { return gfx::Texture::LoadFromFile(key); }
|
||||||
};
|
};
|
||||||
|
#endif // CLIENT
|
||||||
|
|
||||||
class SkeletonCache final : public Cache<Skeleton>
|
class SkeletonCache final : public Cache<Skeleton>
|
||||||
{
|
{
|
||||||
@ -54,26 +63,45 @@ protected:
|
|||||||
PtrType Load(const std::string& key) override { return Model::LoadFromFile(key); }
|
PtrType Load(const std::string& key) override { return Model::LoadFromFile(key); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MapCache final : public Cache<Map>
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
PtrType Load(const std::string& key) override { return Map::LoadFromFile(key); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class VehicleCache final : public Cache<VehicleModel>
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
PtrType Load(const std::string& key) override { return VehicleModel::LoadFromFile(key); }
|
||||||
|
};
|
||||||
|
|
||||||
class CacheManager
|
class CacheManager
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static std::shared_ptr<const gfx::Texture> GetTexture(const std::string& filename) {
|
static std::shared_ptr<const Skeleton> GetSkeleton(const std::string& filename)
|
||||||
return texture_cache_.Get(filename);
|
{
|
||||||
}
|
|
||||||
|
|
||||||
static std::shared_ptr<const Skeleton> GetSkeleton(const std::string& filename) {
|
|
||||||
return skeleton_cache_.Get(filename);
|
return skeleton_cache_.Get(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::shared_ptr<const Model> GetModel(const std::string& filename) {
|
static std::shared_ptr<const Model> GetModel(const std::string& filename) { return model_cache_.Get(filename); }
|
||||||
return model_cache_.Get(filename);
|
|
||||||
|
static std::shared_ptr<const Map> GetMap(const std::string& filename) { return map_cache_.Get(filename); }
|
||||||
|
|
||||||
|
static std::shared_ptr<const VehicleModel> GetVehicleModel(const std::string& filename) { return vehicle_cache_.Get(filename); }
|
||||||
|
|
||||||
|
#ifdef CLIENT
|
||||||
|
static std::shared_ptr<const gfx::Texture> GetTexture(const std::string& filename)
|
||||||
|
{
|
||||||
|
return texture_cache_.Get(filename);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static TextureCache texture_cache_;
|
|
||||||
static SkeletonCache skeleton_cache_;
|
static SkeletonCache skeleton_cache_;
|
||||||
static ModelCache model_cache_;
|
static ModelCache model_cache_;
|
||||||
|
static MapCache map_cache_;
|
||||||
|
static VehicleCache vehicle_cache_;
|
||||||
|
CLIENT_ONLY(static TextureCache texture_cache_;)
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace assets
|
} // namespace assets
|
||||||
@ -47,3 +47,20 @@ std::shared_ptr<const assets::Map> assets::Map::LoadFromFile(const std::string&
|
|||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CLIENT
|
||||||
|
void assets::Map::Draw(gfx::DrawList& dlist) const
|
||||||
|
{
|
||||||
|
if (!basemodel_ || !basemodel_->GetMesh())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto& surfaces = basemodel_->GetMesh()->surfaces;
|
||||||
|
|
||||||
|
for (const auto& surface : surfaces)
|
||||||
|
{
|
||||||
|
gfx::DrawSurfaceCmd cmd;
|
||||||
|
cmd.surface = &surface;
|
||||||
|
dlist.AddSurface(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // CLIENT
|
||||||
@ -6,6 +6,10 @@
|
|||||||
#include "model.hpp"
|
#include "model.hpp"
|
||||||
#include "utils/transform.hpp"
|
#include "utils/transform.hpp"
|
||||||
|
|
||||||
|
#ifdef CLIENT
|
||||||
|
#include "gfx/draw_list.hpp"
|
||||||
|
#endif // CLIENT
|
||||||
|
|
||||||
namespace assets
|
namespace assets
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -22,6 +26,11 @@ public:
|
|||||||
Map() = default;
|
Map() = default;
|
||||||
static std::shared_ptr<const Map> LoadFromFile(const std::string& filename);
|
static std::shared_ptr<const Map> LoadFromFile(const std::string& filename);
|
||||||
|
|
||||||
|
const std::shared_ptr<const Model>& GetBaseModel() const { return basemodel_; }
|
||||||
|
const std::vector<MapStaticObject>& GetStaticObjects() const { return static_objects_; }
|
||||||
|
|
||||||
|
CLIENT_ONLY(void Draw(gfx::DrawList& dlist) const;)
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<const Model> basemodel_;
|
std::shared_ptr<const Model> basemodel_;
|
||||||
std::vector<MapStaticObject> static_objects_;
|
std::vector<MapStaticObject> static_objects_;
|
||||||
|
|||||||
@ -13,7 +13,7 @@ void assets::MeshBuilder::BeginSurface(gfx::SurfaceFlags sflags, const std::stri
|
|||||||
|
|
||||||
gfx::Surface surface;
|
gfx::Surface surface;
|
||||||
surface.sflags = sflags;
|
surface.sflags = sflags;
|
||||||
surface.texture = texture;
|
surface.texture = std::move(texture);
|
||||||
surface.first = tris_.size();
|
surface.first = tris_.size();
|
||||||
mesh_->surfaces.push_back(surface);
|
mesh_->surfaces.push_back(surface);
|
||||||
|
|
||||||
|
|||||||
@ -6,32 +6,53 @@
|
|||||||
std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::string& filename)
|
std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::string& filename)
|
||||||
{
|
{
|
||||||
auto model = std::make_shared<Model>();
|
auto model = std::make_shared<Model>();
|
||||||
|
std::vector<glm::vec3> vert_pos; // rember for collision trimesh
|
||||||
|
|
||||||
MeshBuilder mb(gfx::MF_NONE);
|
CLIENT_ONLY(MeshBuilder mb(gfx::MF_NONE);)
|
||||||
|
|
||||||
LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) {
|
LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) {
|
||||||
if (command == "v")
|
if (command == "v")
|
||||||
{
|
{
|
||||||
MeshVertex v;
|
glm::vec3 pos;
|
||||||
iss >> v.pos.x >> v.pos.y >> v.pos.z;
|
iss >> pos.x >> pos.y >> pos.z;
|
||||||
iss >> v.normal.x >> v.normal.y >> v.normal.z;
|
|
||||||
iss >> v.uv.x >> v.uv.y;
|
|
||||||
|
|
||||||
// TODO: LUV & bone data
|
CLIENT_ONLY(
|
||||||
|
MeshVertex v;
|
||||||
|
v.pos = pos;
|
||||||
|
iss >> v.normal.x >> v.normal.y >> v.normal.z;
|
||||||
|
iss >> v.uv.x >> v.uv.y;
|
||||||
|
|
||||||
mb.AddVertex(v);
|
// TODO: LUV & bone data
|
||||||
|
|
||||||
|
mb.AddVertex(v);
|
||||||
|
)
|
||||||
|
|
||||||
|
if (model->cmesh_)
|
||||||
|
vert_pos.emplace_back(pos);
|
||||||
}
|
}
|
||||||
else if (command == "f")
|
else if (command == "f")
|
||||||
{
|
{
|
||||||
MeshTriangle t;
|
uint32_t indices[3];
|
||||||
iss >> t.vert[0] >> t.vert[1] >> t.vert[2];
|
iss >> indices[0] >> indices[1] >> indices[2];
|
||||||
|
|
||||||
mb.AddTriangle(t);
|
CLIENT_ONLY(
|
||||||
|
MeshTriangle t;
|
||||||
|
t.vert[0] = indices[0];
|
||||||
|
t.vert[1] = indices[1];
|
||||||
|
t.vert[2] = indices[2];
|
||||||
|
mb.AddTriangle(t);
|
||||||
|
)
|
||||||
|
|
||||||
|
if (model->cmesh_)
|
||||||
|
{
|
||||||
|
// FIXME: possible index segfault
|
||||||
|
model->cmesh_->AddTriangle(vert_pos[indices[0]], vert_pos[indices[1]], vert_pos[indices[2]]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (command == "surface")
|
else if (command == "surface")
|
||||||
{
|
{
|
||||||
std::string surface_name, texture_name;
|
std::string surface_name, texture_name;
|
||||||
gfx::SurfaceFlags sflags = gfx::SF_NONE;
|
CLIENT_ONLY(gfx::SurfaceFlags sflags = gfx::SF_NONE;)
|
||||||
|
|
||||||
iss >> surface_name;
|
iss >> surface_name;
|
||||||
|
|
||||||
@ -40,22 +61,36 @@ std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::stri
|
|||||||
while (iss >> flag)
|
while (iss >> flag)
|
||||||
{
|
{
|
||||||
if (flag == "+texture")
|
if (flag == "+texture")
|
||||||
|
{
|
||||||
iss >> texture_name;
|
iss >> texture_name;
|
||||||
|
}
|
||||||
else if (flag == "+doublesided")
|
else if (flag == "+doublesided")
|
||||||
sflags |= gfx::SF_DOUBLE_SIDED;
|
{
|
||||||
|
CLIENT_ONLY(sflags |= gfx::SF_DOUBLE_SIDED;)
|
||||||
|
}
|
||||||
else if (flag == "+transparent")
|
else if (flag == "+transparent")
|
||||||
sflags |= gfx::SF_TRANSPARENT;
|
{
|
||||||
|
CLIENT_ONLY(sflags |= gfx::SF_TRANSPARENT;)
|
||||||
|
}
|
||||||
else if (flag == "+ocolor")
|
else if (flag == "+ocolor")
|
||||||
sflags |= gfx::SF_OBJECT_COLOR;
|
{
|
||||||
|
CLIENT_ONLY(sflags |= gfx::SF_OBJECT_COLOR;)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<const gfx::Texture> texture;
|
CLIENT_ONLY(
|
||||||
if (!texture_name.empty())
|
std::shared_ptr<const gfx::Texture> texture;
|
||||||
{
|
if (!texture_name.empty())
|
||||||
texture = CacheManager::GetTexture("data/" + surface_name + ".png");
|
{
|
||||||
}
|
texture = CacheManager::GetTexture("data/" + surface_name + ".png");
|
||||||
|
}
|
||||||
|
|
||||||
mb.BeginSurface(sflags, surface_name, texture);
|
mb.BeginSurface(sflags, surface_name, texture);
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else if (command == "makecoltrimesh")
|
||||||
|
{
|
||||||
|
model->cmesh_ = std::make_unique<collision::TriangleMesh>();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -65,7 +100,13 @@ std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::stri
|
|||||||
// TODO: skeleton
|
// TODO: skeleton
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CLIENT_ONLY(
|
||||||
|
mb.Build();
|
||||||
|
model->mesh_ = mb.GetMesh();
|
||||||
|
)
|
||||||
|
|
||||||
|
if (model->cmesh_)
|
||||||
|
model->cmesh_->Build();
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,55 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "utils/defs.hpp"
|
||||||
|
|
||||||
|
#include "collision/trianglemesh.hpp"
|
||||||
|
|
||||||
|
#ifdef CLIENT
|
||||||
#include "mesh_builder.hpp"
|
#include "mesh_builder.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace assets
|
namespace assets
|
||||||
{
|
{
|
||||||
|
|
||||||
|
enum ModelCollisionShapeType
|
||||||
|
{
|
||||||
|
MCS_NONE,
|
||||||
|
|
||||||
|
MCS_BOX,
|
||||||
|
MCS_SPHERE,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ModelCollisionShape
|
||||||
|
{
|
||||||
|
ModelCollisionShapeType type = MCS_NONE;
|
||||||
|
glm::vec3 origin = glm::vec3(0.0f);
|
||||||
|
union
|
||||||
|
{
|
||||||
|
float radius;
|
||||||
|
glm::vec3 half_extents;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
class Model
|
class Model
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Model() = default;
|
Model() = default;
|
||||||
static std::shared_ptr<const Model> LoadFromFile(const std::string& filename);
|
static std::shared_ptr<const Model> LoadFromFile(const std::string& filename);
|
||||||
|
|
||||||
const std::shared_ptr<const Mesh>& GetMesh() const { return mesh_; }
|
const collision::TriangleMesh* GetColMesh() const { return cmesh_.get(); }
|
||||||
|
const std::vector<ModelCollisionShape>& GetColShapes() const { return cshapes_; }
|
||||||
|
|
||||||
|
CLIENT_ONLY(const std::shared_ptr<const Mesh>& GetMesh() const { return mesh_; })
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<const Mesh> mesh_;
|
std::unique_ptr<collision::TriangleMesh> cmesh_;
|
||||||
|
std::vector<ModelCollisionShape> cshapes_;
|
||||||
|
|
||||||
|
CLIENT_ONLY(std::shared_ptr<const Mesh> mesh_;)
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
43
src/assets/vehiclemdl.cpp
Normal file
43
src/assets/vehiclemdl.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#include "vehiclemdl.hpp"
|
||||||
|
|
||||||
|
#include "cache.hpp"
|
||||||
|
#include "cmdfile.hpp"
|
||||||
|
|
||||||
|
std::shared_ptr<const assets::VehicleModel> assets::VehicleModel::LoadFromFile(const std::string& filename)
|
||||||
|
{
|
||||||
|
auto veh = std::shared_ptr<VehicleModel>();
|
||||||
|
|
||||||
|
LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) {
|
||||||
|
if (command == "basemodel")
|
||||||
|
{
|
||||||
|
std::string model_name;
|
||||||
|
iss >> model_name;
|
||||||
|
|
||||||
|
veh->basemodel_ = CacheManager::GetModel("data/" + model_name + ".mdl");
|
||||||
|
}
|
||||||
|
else if (command == "wheel")
|
||||||
|
{
|
||||||
|
VehicleWheel wheel;
|
||||||
|
|
||||||
|
std::string type_str;
|
||||||
|
std::string model_name;
|
||||||
|
iss >> type_str >> model_name;
|
||||||
|
|
||||||
|
if (type_str == "FL")
|
||||||
|
wheel.type = WHEEL_FL;
|
||||||
|
else if (type_str == "FR")
|
||||||
|
wheel.type = WHEEL_FR;
|
||||||
|
else if (type_str == "RL")
|
||||||
|
wheel.type = WHEEL_RL;
|
||||||
|
else if (type_str == "RR")
|
||||||
|
wheel.type = WHEEL_RR;
|
||||||
|
|
||||||
|
wheel.model = assets::CacheManager::GetModel("data/" + model_name + ".mdl");
|
||||||
|
|
||||||
|
iss >> wheel.position.x >> wheel.position.y >> wheel.position.z;
|
||||||
|
iss >> wheel.radius;
|
||||||
|
|
||||||
|
veh->wheels_.emplace_back(wheel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
45
src/assets/vehiclemdl.hpp
Normal file
45
src/assets/vehiclemdl.hpp
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "model.hpp"
|
||||||
|
#include "utils/transform.hpp"
|
||||||
|
|
||||||
|
namespace assets
|
||||||
|
{
|
||||||
|
|
||||||
|
enum VehicleWheelType
|
||||||
|
{
|
||||||
|
WHEEL_REAR = 1,
|
||||||
|
WHEEL_RIGHT = 2,
|
||||||
|
|
||||||
|
WHEEL_FL = 0,
|
||||||
|
WHEEL_FR = WHEEL_RIGHT,
|
||||||
|
WHEEL_RL = WHEEL_REAR | WHEEL_RIGHT,
|
||||||
|
WHEEL_RR = WHEEL_REAR | WHEEL_RIGHT,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VehicleWheel
|
||||||
|
{
|
||||||
|
VehicleWheelType type = WHEEL_FL;
|
||||||
|
std::shared_ptr<const Model> model;
|
||||||
|
glm::vec3 position;
|
||||||
|
float radius;
|
||||||
|
};
|
||||||
|
|
||||||
|
class VehicleModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VehicleModel() = default;
|
||||||
|
|
||||||
|
static std::shared_ptr<const VehicleModel> LoadFromFile(const std::string& filename);
|
||||||
|
|
||||||
|
const std::shared_ptr<const Model>& GetModel() { return basemodel_; }
|
||||||
|
const std::vector<VehicleWheel>& GetWheels() { return wheels_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<const Model> basemodel_;
|
||||||
|
std::vector<VehicleWheel> wheels_;
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "net/defs.hpp"
|
||||||
|
#include "net/outmessage.hpp"
|
||||||
|
|
||||||
|
#include "gameview/worldview.hpp"
|
||||||
|
|
||||||
App::App()
|
App::App()
|
||||||
{
|
{
|
||||||
std::cout << "Initializing App..." << std::endl;
|
std::cout << "Initializing App..." << std::endl;
|
||||||
@ -30,8 +35,53 @@ void App::Frame()
|
|||||||
float aspect = static_cast<float>(viewport_size_.x) / static_cast<float>(viewport_size_.y);
|
float aspect = static_cast<float>(viewport_size_.x) / static_cast<float>(viewport_size_.y);
|
||||||
|
|
||||||
renderer_.Begin(viewport_size_.x, viewport_size_.y);
|
renderer_.Begin(viewport_size_.x, viewport_size_.y);
|
||||||
|
renderer_.ClearColor(glm::vec3(0.3f, 0.9f, 1.0f));
|
||||||
|
renderer_.ClearDepth();
|
||||||
|
|
||||||
// TODO: draw world
|
dlist_.Clear();
|
||||||
|
|
||||||
|
const game::view::WorldView* world;
|
||||||
|
if (session_ && (world = session_->GetWorld()))
|
||||||
|
{
|
||||||
|
world->Draw(dlist_);
|
||||||
|
|
||||||
|
glm::mat4 view = glm::lookAt(glm::vec3(80.0f, 0.0f, 10.0f), glm::vec3(40.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||||
|
glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 3000.0f);
|
||||||
|
|
||||||
|
gfx::DrawListParams params;
|
||||||
|
params.view_proj = proj * view;
|
||||||
|
|
||||||
|
renderer_.DrawList(dlist_, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::Connected()
|
||||||
|
{
|
||||||
|
std::cout << "WS connected" << std::endl;
|
||||||
|
|
||||||
|
// init session
|
||||||
|
session_ = std::make_unique<game::view::ClientSession>();
|
||||||
|
|
||||||
|
// send login
|
||||||
|
auto msg = BeginMsg(net::MSG_ID);
|
||||||
|
net::PlayerName name;
|
||||||
|
msg.Write(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::ProcessMessage(net::InMessage& msg)
|
||||||
|
{
|
||||||
|
if (!session_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
session_->ProcessMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::Disconnected(const std::string& reason)
|
||||||
|
{
|
||||||
|
std::cout << "WS disconnected" << std::endl;
|
||||||
|
|
||||||
|
// close session
|
||||||
|
session_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::MouseMove(const glm::vec2& delta)
|
void App::MouseMove(const glm::vec2& delta)
|
||||||
|
|||||||
@ -1,29 +1,46 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#include "game/player_input.hpp"
|
#include "game/player_input.hpp"
|
||||||
#include "gfx/renderer.hpp"
|
#include "gfx/renderer.hpp"
|
||||||
|
#include "net/msg_producer.hpp"
|
||||||
|
#include "net/inmessage.hpp"
|
||||||
|
|
||||||
class App
|
#include "gameview/client_session.hpp"
|
||||||
|
|
||||||
|
class App : public net::MsgProducer
|
||||||
{
|
{
|
||||||
float time_ = 0.0f;
|
|
||||||
glm::ivec2 viewport_size_ = { 800, 600 };
|
|
||||||
game::PlayerInputFlags input_ = 0;
|
|
||||||
game::PlayerInputFlags prev_input_ = 0;
|
|
||||||
|
|
||||||
float prev_time_ = 0.0f;
|
|
||||||
|
|
||||||
gfx::Renderer renderer_;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
App();
|
App();
|
||||||
|
|
||||||
void Frame();
|
void Frame();
|
||||||
|
|
||||||
|
void Connected();
|
||||||
|
void ProcessMessage(net::InMessage& msg);
|
||||||
|
void Disconnected(const std::string& reason);
|
||||||
|
|
||||||
void SetTime(float time) { time_ = time; }
|
void SetTime(float time) { time_ = time; }
|
||||||
void SetViewportSize(int width, int height) { viewport_size_ = { width, height }; }
|
void SetViewportSize(int width, int height) { viewport_size_ = {width, height}; }
|
||||||
void SetInput(game::PlayerInputFlags input) { input_ = input; }
|
void SetInput(game::PlayerInputFlags input) { input_ = input; }
|
||||||
void MouseMove(const glm::vec2& delta);
|
void MouseMove(const glm::vec2& delta);
|
||||||
|
|
||||||
~App();
|
~App();
|
||||||
};
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Send(std::vector<char> data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
float time_ = 0.0f;
|
||||||
|
glm::ivec2 viewport_size_ = {800, 600};
|
||||||
|
game::PlayerInputFlags input_ = 0;
|
||||||
|
game::PlayerInputFlags prev_input_ = 0;
|
||||||
|
|
||||||
|
float prev_time_ = 0.0f;
|
||||||
|
|
||||||
|
gfx::Renderer renderer_;
|
||||||
|
gfx::DrawList dlist_;
|
||||||
|
|
||||||
|
std::unique_ptr<game::view::ClientSession> session_;
|
||||||
|
};
|
||||||
|
|||||||
@ -1,13 +1,16 @@
|
|||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "app.hpp"
|
#include <vector>
|
||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
#include <emscripten/html5_webgl.h>
|
#include <emscripten/html5_webgl.h>
|
||||||
#endif
|
#else
|
||||||
|
#include <easywsclient.hpp>
|
||||||
|
#endif // EMSCRIPTEN
|
||||||
|
|
||||||
|
#include "app.hpp"
|
||||||
#include "gl.hpp"
|
#include "gl.hpp"
|
||||||
|
|
||||||
static SDL_Window *s_window = nullptr;
|
static SDL_Window *s_window = nullptr;
|
||||||
@ -155,39 +158,39 @@ static void Frame()
|
|||||||
const uint8_t* kbd_state = SDL_GetKeyboardState(nullptr);
|
const uint8_t* kbd_state = SDL_GetKeyboardState(nullptr);
|
||||||
|
|
||||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_w)])
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_w)])
|
||||||
input |= game::PI_FORWARD;
|
input |= game::IN_FORWARD;
|
||||||
|
|
||||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_s)])
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_s)])
|
||||||
input |= game::PI_BACKWARD;
|
input |= game::IN_BACKWARD;
|
||||||
|
|
||||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_a)])
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_a)])
|
||||||
input |= game::PI_LEFT;
|
input |= game::IN_LEFT;
|
||||||
|
|
||||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_d)])
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_d)])
|
||||||
input |= game::PI_RIGHT;
|
input |= game::IN_RIGHT;
|
||||||
|
|
||||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_SPACE)])
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_SPACE)])
|
||||||
input |= game::PI_JUMP;
|
input |= game::IN_JUMP;
|
||||||
|
|
||||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_LCTRL)])
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_LCTRL)])
|
||||||
input |= game::PI_CROUCH;
|
input |= game::IN_CROUCH;
|
||||||
|
|
||||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_e)])
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_e)])
|
||||||
input |= game::PI_USE;
|
input |= game::IN_USE;
|
||||||
|
|
||||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_F3)])
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_F3)])
|
||||||
input |= game::PI_DEBUG1;
|
input |= game::IN_DEBUG1;
|
||||||
|
|
||||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_F4)])
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_F4)])
|
||||||
input |= game::PI_DEBUG2;
|
input |= game::IN_DEBUG2;
|
||||||
|
|
||||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_F5)])
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_F5)])
|
||||||
input |= game::PI_DEBUG3;
|
input |= game::IN_DEBUG3;
|
||||||
|
|
||||||
int mouse_state = SDL_GetMouseState(nullptr, nullptr);
|
int mouse_state = SDL_GetMouseState(nullptr, nullptr);
|
||||||
|
|
||||||
if (mouse_state & SDL_BUTTON(SDL_BUTTON_LEFT))
|
if (mouse_state & SDL_BUTTON(SDL_BUTTON_LEFT))
|
||||||
input |= game::PI_ATTACK;
|
input |= game::IN_ATTACK;
|
||||||
|
|
||||||
|
|
||||||
s_app->SetInput(input);
|
s_app->SetInput(input);
|
||||||
@ -216,16 +219,58 @@ static void Main() {
|
|||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
emscripten_set_main_loop(Frame, 0, true);
|
emscripten_set_main_loop(Frame, 0, true);
|
||||||
#else
|
#else
|
||||||
while (!s_quit)
|
|
||||||
{
|
{
|
||||||
Frame();
|
using namespace easywsclient;
|
||||||
|
|
||||||
|
auto ws = std::unique_ptr<WebSocket>(WebSocket::from_url("ws://127.0.0.1:8080/ws"));
|
||||||
|
bool connected = false;
|
||||||
|
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
|
||||||
|
while (!s_quit)
|
||||||
|
{
|
||||||
|
ws->poll();
|
||||||
|
|
||||||
|
auto ws_state = ws->getReadyState();
|
||||||
|
if (ws_state == WebSocket::OPEN && !connected)
|
||||||
|
{
|
||||||
|
connected = true;
|
||||||
|
s_app->Connected();
|
||||||
|
}
|
||||||
|
else if (ws_state != WebSocket::CLOSED && connected)
|
||||||
|
{
|
||||||
|
connected = false;
|
||||||
|
s_app->Disconnected("WS closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
ws->dispatchBinary([&](const std::vector<uint8_t>& data) {
|
||||||
|
net::InMessage msg(reinterpret_cast<const char*>(data.data()), data.size());
|
||||||
|
s_app->ProcessMessage(msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
Frame();
|
||||||
|
|
||||||
|
if (connected)
|
||||||
|
{
|
||||||
|
auto msg = s_app->GetMsg();
|
||||||
|
if (!msg.empty())
|
||||||
|
{
|
||||||
|
data.resize(msg.size_bytes());
|
||||||
|
memcpy(data.data(), msg.data(), msg.size_bytes());
|
||||||
|
ws->sendBinary(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s_app->ResetMsg();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s_app.reset();
|
s_app.reset();
|
||||||
|
|
||||||
ShutdownGL();
|
ShutdownGL();
|
||||||
ShutdownSDL();
|
ShutdownSDL();
|
||||||
#endif
|
#endif // EMSCRIPTEN
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
|
|||||||
52
src/collision/dynamicsworld.cpp
Normal file
52
src/collision/dynamicsworld.cpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#include "dynamicsworld.hpp"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
collision::DynamicsWorld::DynamicsWorld(std::shared_ptr<const assets::Map> map)
|
||||||
|
: map_(std::move(map)), bt_dispatcher_(&bt_cfg_),
|
||||||
|
bt_world_(&bt_dispatcher_, &bt_broadphase_, &bt_solver_, &bt_cfg_), bt_veh_raycaster_(&bt_world_)
|
||||||
|
{
|
||||||
|
AddMapCollision();
|
||||||
|
}
|
||||||
|
|
||||||
|
void collision::DynamicsWorld::AddMapCollision()
|
||||||
|
{
|
||||||
|
if (!map_) // is perfectly possible that there is no map in this world
|
||||||
|
return;
|
||||||
|
|
||||||
|
// add basemodel
|
||||||
|
const auto& basemodel = map_->GetBaseModel();
|
||||||
|
if (basemodel)
|
||||||
|
{
|
||||||
|
Transform identity;
|
||||||
|
AddModelInstance(*basemodel, identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add static objects
|
||||||
|
for (const auto& sobjs = map_->GetStaticObjects(); const auto& sobj : sobjs)
|
||||||
|
{
|
||||||
|
AddModelInstance(*sobj.model, sobj.transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void collision::DynamicsWorld::AddModelInstance(const assets::Model& model, const Transform& trans)
|
||||||
|
{
|
||||||
|
if (auto cmesh = model.GetColMesh(); cmesh)
|
||||||
|
{
|
||||||
|
// create trimesh object
|
||||||
|
auto obj = std::make_unique<btCollisionObject>();
|
||||||
|
obj->setCollisionShape(cmesh->GetShape());
|
||||||
|
|
||||||
|
// set transform
|
||||||
|
obj->setWorldTransform(trans.ToBtTransform());
|
||||||
|
|
||||||
|
// add to world
|
||||||
|
bt_world_.addCollisionObject(obj.get());
|
||||||
|
static_objs_.emplace_back(std::move(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& shapes = model.GetColShapes(); const auto& shape : shapes)
|
||||||
|
{
|
||||||
|
// TODO: add basic shapes
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/collision/dynamicsworld.hpp
Normal file
42
src/collision/dynamicsworld.hpp
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include <btBulletDynamicsCommon.h>
|
||||||
|
|
||||||
|
#include "assets/map.hpp"
|
||||||
|
|
||||||
|
namespace collision
|
||||||
|
{
|
||||||
|
|
||||||
|
class DynamicsWorld
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DynamicsWorld(std::shared_ptr<const assets::Map> map);
|
||||||
|
|
||||||
|
btDynamicsWorld& GetBtWorld() { return bt_world_; }
|
||||||
|
const btDynamicsWorld& GetBtWorld() const { return bt_world_; }
|
||||||
|
btVehicleRaycaster& GetVehicleRaycaster() { return bt_veh_raycaster_; }
|
||||||
|
|
||||||
|
const std::shared_ptr<const assets::Map>& GetMap() const { return map_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void AddMapCollision();
|
||||||
|
void AddModelInstance(const assets::Model& model, const Transform& trans);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// this is BEFORE bt_world_!!!
|
||||||
|
std::shared_ptr<const assets::Map> map_;
|
||||||
|
std::vector<std::unique_ptr<btCollisionObject>> static_objs_;
|
||||||
|
// ^-----
|
||||||
|
|
||||||
|
btDefaultCollisionConfiguration bt_cfg_;
|
||||||
|
btCollisionDispatcher bt_dispatcher_;
|
||||||
|
btDbvtBroadphase bt_broadphase_;
|
||||||
|
btSequentialImpulseConstraintSolver bt_solver_;
|
||||||
|
btDiscreteDynamicsWorld bt_world_;
|
||||||
|
btDefaultVehicleRaycaster bt_veh_raycaster_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace collision
|
||||||
26
src/collision/motionstate.hpp
Normal file
26
src/collision/motionstate.hpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "utils/transform.hpp"
|
||||||
|
#include <btBulletDynamicsCommon.h>
|
||||||
|
|
||||||
|
namespace collision
|
||||||
|
{
|
||||||
|
|
||||||
|
class MotionState : public btMotionState
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MotionState(Transform& transform) : transform_(transform) {}
|
||||||
|
|
||||||
|
void getWorldTransform(btTransform& bt_trans) const override {
|
||||||
|
bt_trans = transform_.ToBtTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setWorldTransform(const btTransform& bt_trans) override {
|
||||||
|
transform_.SetBtTransform(bt_trans);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Transform& transform_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@ -22,6 +22,6 @@ namespace collision
|
|||||||
void AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2);
|
void AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2);
|
||||||
void Build();
|
void Build();
|
||||||
|
|
||||||
btBvhTriangleMeshShape* GetShape() { return bt_shape_.get(); }
|
btBvhTriangleMeshShape* GetShape() const { return bt_shape_.get(); }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
5
src/game/entity.cpp
Normal file
5
src/game/entity.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#include "entity.hpp"
|
||||||
|
|
||||||
|
#include "world.hpp"
|
||||||
|
|
||||||
|
game::Entity::Entity(World& world, net::EntType viewtype) : world_(world), entnum_(world.GetNewEntnum()), viewtype_(viewtype) {}
|
||||||
33
src/game/entity.hpp
Normal file
33
src/game/entity.hpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "net/msg_producer.hpp"
|
||||||
|
#include "transform_node.hpp"
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
|
||||||
|
class World;
|
||||||
|
class Player;
|
||||||
|
|
||||||
|
class Entity : public net::MsgProducer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Entity(World& world, net::EntType viewtype);
|
||||||
|
|
||||||
|
net::EntNum GetEntNum() const { return entnum_; }
|
||||||
|
net::EntType GetViewType() const { return viewtype_; }
|
||||||
|
|
||||||
|
virtual void Update() { ResetMsg(); }
|
||||||
|
virtual void SendInitData(Player& player, net::OutMessage& msg) const {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
World& world_;
|
||||||
|
const net::EntNum entnum_;
|
||||||
|
const net::EntType viewtype_;
|
||||||
|
|
||||||
|
TransformNode root_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
8
src/game/game.cpp
Normal file
8
src/game/game.cpp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#include "game.hpp"
|
||||||
|
|
||||||
|
game::Game::Game()
|
||||||
|
{
|
||||||
|
default_world_ = std::make_shared<World>("openworld");
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
20
src/game/game.hpp
Normal file
20
src/game/game.hpp
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "world.hpp"
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
|
||||||
|
class Game
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Game();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<World> default_world_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
114
src/game/player.cpp
Normal file
114
src/game/player.cpp
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#include "player.hpp"
|
||||||
|
|
||||||
|
#include "world.hpp"
|
||||||
|
|
||||||
|
game::Player::Player(Game& game, std::string name) : game_(game), name_(std::move(name)) {}
|
||||||
|
|
||||||
|
bool game::Player::ProcessMsg(net::MessageType type, net::InMessage& msg)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case net::MSG_IN:
|
||||||
|
return ProcessInputMsg(msg);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Player::Update()
|
||||||
|
{
|
||||||
|
if (world_ != known_world_)
|
||||||
|
{
|
||||||
|
SendWorldMsg();
|
||||||
|
known_world_ = world_;
|
||||||
|
known_ents_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (world_)
|
||||||
|
SyncEntities();
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Player::SendWorldMsg()
|
||||||
|
{
|
||||||
|
auto msg = BeginMsg(net::MSG_CHWORLD);
|
||||||
|
msg.Write(net::MapName(world_->GetMapName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Player::SyncEntities()
|
||||||
|
{
|
||||||
|
const auto& ents = world_->GetEntities();
|
||||||
|
|
||||||
|
auto ent_it = ents.begin();
|
||||||
|
auto know_it = known_ents_.begin();
|
||||||
|
|
||||||
|
while (ent_it != ents.end() || know_it != known_ents_.end())
|
||||||
|
{
|
||||||
|
const net::EntNum entnum = (ent_it != ents.end() ? ent_it->first : std::numeric_limits<net::EntNum>::max());
|
||||||
|
const net::EntNum knownum = (know_it != known_ents_.end() ? *know_it : std::numeric_limits<net::EntNum>::max());
|
||||||
|
|
||||||
|
if (entnum == knownum) // ----- entity exists and is currently known -----
|
||||||
|
{
|
||||||
|
const Entity& e = *ent_it->second;
|
||||||
|
if (ShouldSeeEntity(e)) // still visible?
|
||||||
|
{
|
||||||
|
SendUpdateEntity(e); // 2) update
|
||||||
|
++ent_it;
|
||||||
|
++know_it;
|
||||||
|
}
|
||||||
|
else // vanished for player
|
||||||
|
{
|
||||||
|
SendDestroyEntity(knownum); // 3) destroy
|
||||||
|
know_it = known_ents_.erase(know_it); // remove from known
|
||||||
|
++ent_it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (entnum < knownum) // ----- entity exists, player does NOT know it -----
|
||||||
|
{
|
||||||
|
const Entity& e = *ent_it->second;
|
||||||
|
if (ShouldSeeEntity(e)) // 1) become visible
|
||||||
|
{
|
||||||
|
SendInitEntity(e);
|
||||||
|
known_ents_.insert(entnum); // add to known
|
||||||
|
} // else: stays invisible, nothing to do
|
||||||
|
++ent_it;
|
||||||
|
}
|
||||||
|
else // ----- player knows it, but it no longer exists -----
|
||||||
|
{
|
||||||
|
SendDestroyEntity(knownum); // 3) destroy
|
||||||
|
know_it = known_ents_.erase(know_it); // remove from known
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::Player::ShouldSeeEntity(const Entity& entity) const
|
||||||
|
{
|
||||||
|
return true; // TODO: check distance?
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Player::SendInitEntity(const Entity& entity)
|
||||||
|
{
|
||||||
|
auto msg = BeginMsg(net::MSG_ENTSPAWN);
|
||||||
|
msg.Write(entity.GetEntNum());
|
||||||
|
msg.Write(entity.GetViewType());
|
||||||
|
entity.SendInitData(*this, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Player::SendUpdateEntity(const Entity& entity)
|
||||||
|
{
|
||||||
|
auto msg = BeginMsg(); // no CMD here, these are already included in entity message payload!
|
||||||
|
msg.Write(entity.GetMsg());
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Player::SendDestroyEntity(net::EntNum entnum)
|
||||||
|
{
|
||||||
|
auto msg = BeginMsg(net::MSG_ENTDESTROY);
|
||||||
|
msg.Write(entnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::Player::ProcessInputMsg(net::InMessage& msg)
|
||||||
|
{
|
||||||
|
if (!msg.Read(in_))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
59
src/game/player.hpp
Normal file
59
src/game/player.hpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "server/client.hpp"
|
||||||
|
#include "net/msg_producer.hpp"
|
||||||
|
|
||||||
|
#include "player_input.hpp"
|
||||||
|
#include "game.hpp"
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
|
||||||
|
class World;
|
||||||
|
class Entity;
|
||||||
|
|
||||||
|
enum PlayerState
|
||||||
|
{
|
||||||
|
PS_NONE,
|
||||||
|
PS_CHARACTER,
|
||||||
|
PS_VEHICLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Player : public net::MsgProducer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Player(Game& game, std::string name);
|
||||||
|
|
||||||
|
bool ProcessMsg(net::MessageType type, net::InMessage& msg);
|
||||||
|
void Update();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SendWorldMsg();
|
||||||
|
|
||||||
|
// entities sync
|
||||||
|
void SyncEntities();
|
||||||
|
bool ShouldSeeEntity(const Entity& entity) const;
|
||||||
|
void SendInitEntity(const Entity& entity);
|
||||||
|
void SendUpdateEntity(const Entity& entity);
|
||||||
|
void SendDestroyEntity(net::EntNum entnum);
|
||||||
|
|
||||||
|
// msg handlers
|
||||||
|
bool ProcessInputMsg(net::InMessage& msg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Game& game_;
|
||||||
|
std::string name_;
|
||||||
|
|
||||||
|
World* world_ = nullptr;
|
||||||
|
World* known_world_ = nullptr;
|
||||||
|
std::set<net::EntNum> known_ents_;
|
||||||
|
|
||||||
|
PlayerInputFlags in_;
|
||||||
|
|
||||||
|
PlayerState state_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@ -8,16 +8,16 @@ namespace game
|
|||||||
|
|
||||||
enum PlayerInputFlag : PlayerInputFlags
|
enum PlayerInputFlag : PlayerInputFlags
|
||||||
{
|
{
|
||||||
PI_FORWARD = 1 << 0,
|
IN_FORWARD = 1 << 0,
|
||||||
PI_BACKWARD = 1 << 1,
|
IN_BACKWARD = 1 << 1,
|
||||||
PI_LEFT = 1 << 2,
|
IN_LEFT = 1 << 2,
|
||||||
PI_RIGHT = 1 << 3,
|
IN_RIGHT = 1 << 3,
|
||||||
PI_JUMP = 1 << 4,
|
IN_JUMP = 1 << 4,
|
||||||
PI_CROUCH = 1 << 5,
|
IN_CROUCH = 1 << 5,
|
||||||
PI_USE = 1 << 6,
|
IN_USE = 1 << 6,
|
||||||
PI_ATTACK = 1 << 7,
|
IN_ATTACK = 1 << 7,
|
||||||
PI_DEBUG1 = 1 << 8,
|
IN_DEBUG1 = 1 << 8,
|
||||||
PI_DEBUG2 = 1 << 9,
|
IN_DEBUG2 = 1 << 9,
|
||||||
PI_DEBUG3 = 1 << 10,
|
IN_DEBUG3 = 1 << 10,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ namespace game
|
|||||||
struct TransformNode
|
struct TransformNode
|
||||||
{
|
{
|
||||||
const TransformNode* parent = nullptr;
|
const TransformNode* parent = nullptr;
|
||||||
Transform local_transform;
|
Transform local;
|
||||||
glm::mat4 matrix = glm::mat4(1.0f); // Global
|
glm::mat4 matrix = glm::mat4(1.0f); // Global
|
||||||
|
|
||||||
TransformNode()
|
TransformNode()
|
||||||
@ -17,7 +17,7 @@ namespace game
|
|||||||
|
|
||||||
void UpdateMatrix()
|
void UpdateMatrix()
|
||||||
{
|
{
|
||||||
matrix = local_transform.ToMatrix();
|
matrix = local.ToMatrix();
|
||||||
|
|
||||||
if (parent)
|
if (parent)
|
||||||
{
|
{
|
||||||
|
|||||||
34
src/game/vehicle.cpp
Normal file
34
src/game/vehicle.cpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#include "vehicle.hpp"
|
||||||
|
|
||||||
|
#include "assets/cache.hpp"
|
||||||
|
|
||||||
|
static std::shared_ptr<const assets::VehicleModel> LoadVehicleModelByName(const std::string& model_name)
|
||||||
|
{
|
||||||
|
return assets::CacheManager::GetVehicleModel("data/" + model_name + ".map");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
game::Vehicle::Vehicle(World& world, std::string model_name) :
|
||||||
|
Entity(world, net::ET_VEHICLE),
|
||||||
|
model_name_(model_name),
|
||||||
|
model_(LoadVehicleModelByName(model_name)),
|
||||||
|
motion_(root_.local)
|
||||||
|
{
|
||||||
|
// setup chassis rigidbody
|
||||||
|
float mass = 300.0f;
|
||||||
|
static btBoxShape shape(btVector3(1, 1, 1));
|
||||||
|
|
||||||
|
btVector3 local_inertia(0, 0, 0);
|
||||||
|
shape.calculateLocalInertia(mass, local_inertia);
|
||||||
|
|
||||||
|
btRigidBody::btRigidBodyConstructionInfo rb_info(mass, &motion_, &shape, local_inertia);
|
||||||
|
body_ = std::make_unique<btRigidBody>(rb_info);
|
||||||
|
|
||||||
|
// setup vehicle
|
||||||
|
btRaycastVehicle::btVehicleTuning tuning;
|
||||||
|
vehicle_ = std::make_unique<btRaycastVehicle>(tuning, body_.get(), &world_.GetVehicleRaycaster());
|
||||||
|
vehicle_->setCoordinateSystem(0, 2, 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
25
src/game/vehicle.hpp
Normal file
25
src/game/vehicle.hpp
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "entity.hpp"
|
||||||
|
#include "world.hpp"
|
||||||
|
#include "assets/vehiclemdl.hpp"
|
||||||
|
#include "collision/motionstate.hpp"
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
|
||||||
|
class Vehicle : public Entity
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Vehicle(World& world, std::string model_name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string model_name_;
|
||||||
|
std::shared_ptr<const assets::VehicleModel> model_;
|
||||||
|
|
||||||
|
collision::MotionState motion_;
|
||||||
|
std::unique_ptr<btRigidBody> body_;
|
||||||
|
std::unique_ptr<btRaycastVehicle> vehicle_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
44
src/game/world.cpp
Normal file
44
src/game/world.cpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#include "world.hpp"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "assets/cache.hpp"
|
||||||
|
#include "utils/allocnum.hpp"
|
||||||
|
|
||||||
|
static std::shared_ptr<const assets::Map> LoadMapByName(const std::string& mapname)
|
||||||
|
{
|
||||||
|
return assets::CacheManager::GetMap("data/" + mapname + ".map");
|
||||||
|
}
|
||||||
|
|
||||||
|
game::World::World(std::string mapname) : DynamicsWorld(LoadMapByName(mapname)), mapname_(std::move(mapname)) {}
|
||||||
|
|
||||||
|
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<Entity> 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;
|
||||||
|
GetBtWorld().stepSimulation(static_cast<float>(delta_time) * 1000.0f);
|
||||||
|
|
||||||
|
for (auto& [entnum, ent] : ents_)
|
||||||
|
{
|
||||||
|
ent->Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/game/world.hpp
Normal file
45
src/game/world.hpp
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
|
|
||||||
|
#include "assets/map.hpp"
|
||||||
|
#include "collision/dynamicsworld.hpp"
|
||||||
|
#include "entity.hpp"
|
||||||
|
#include "net/defs.hpp"
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
|
||||||
|
class World : public collision::DynamicsWorld
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
World(std::string mapname);
|
||||||
|
|
||||||
|
// spawn entity of type T
|
||||||
|
template <std::derived_from<Entity> T, typename... TArgs>
|
||||||
|
T& Spawn(TArgs&&... args)
|
||||||
|
{
|
||||||
|
auto ent = std::make_unique<T>(*this, std::forward<TArgs>(args)...);
|
||||||
|
auto& ref = *ent;
|
||||||
|
RegisterEntity(std::move(ent));
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
net::EntNum GetNewEntnum();
|
||||||
|
void RegisterEntity(std::unique_ptr<Entity> ent);
|
||||||
|
|
||||||
|
void Update(int64_t delta_time);
|
||||||
|
|
||||||
|
const std::string& GetMapName() const { return mapname_; }
|
||||||
|
const std::map<net::EntNum, std::unique_ptr<Entity>>& GetEntities() const { return ents_; }
|
||||||
|
int64_t GetTime() const { return time_ms_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string mapname_;
|
||||||
|
std::map<net::EntNum, std::unique_ptr<Entity>> ents_;
|
||||||
|
net::EntNum last_entnum_ = 0;
|
||||||
|
|
||||||
|
int64_t time_ms_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace game
|
||||||
46
src/gameview/client_session.cpp
Normal file
46
src/gameview/client_session.cpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#include "client_session.hpp"
|
||||||
|
|
||||||
|
game::view::ClientSession::ClientSession(App& app) : app_(app) {}
|
||||||
|
|
||||||
|
bool game::view::ClientSession::ProcessMessage(net::InMessage& msg)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
net::MessageType type = net::MSG_NONE;
|
||||||
|
if (!msg.Read(type))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (type == net::MSG_NONE || type >= net::MSG_COUNT)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ProcessSingleMessage(type, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::view::ClientSession::ProcessSingleMessage(net::MessageType type, net::InMessage& msg)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case net::MSG_CHWORLD:
|
||||||
|
return ProcessWorldMsg(msg);
|
||||||
|
|
||||||
|
default:
|
||||||
|
// try pass the msg to world
|
||||||
|
if (world_ && world_->ProcessMsg(type, msg))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::view::ClientSession::ProcessWorldMsg(net::InMessage& msg)
|
||||||
|
{
|
||||||
|
net::MapName mapname;
|
||||||
|
if (!msg.Read(mapname))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// TODO: pass mapname
|
||||||
|
world_ = std::make_unique<WorldView>();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@ -1,12 +1,36 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "worldview.hpp"
|
||||||
|
|
||||||
|
#include "client/app.hpp"
|
||||||
|
#include "gfx/draw_list.hpp"
|
||||||
|
#include "net/defs.hpp"
|
||||||
|
#include "net/inmessage.hpp"
|
||||||
|
|
||||||
namespace game::view
|
namespace game::view
|
||||||
{
|
{
|
||||||
|
|
||||||
class ClientSession
|
class ClientSession
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
ClientSession(App& app);
|
||||||
|
|
||||||
|
bool ProcessMessage(net::InMessage& msg);
|
||||||
|
bool ProcessSingleMessage(net::MessageType type, net::InMessage& msg);
|
||||||
|
|
||||||
|
const WorldView* GetWorld() const { return world_.get(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// msg handlers
|
||||||
|
bool ProcessWorldMsg(net::InMessage& msg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
App& app_;
|
||||||
|
|
||||||
|
std::unique_ptr<WorldView> world_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace game::view
|
} // namespace game::view
|
||||||
@ -1,8 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
#include "game/transform_node.hpp"
|
#include "game/transform_node.hpp"
|
||||||
#include "gfx/draw_list.hpp"
|
#include "gfx/draw_list.hpp"
|
||||||
|
|
||||||
|
#include "net/defs.hpp"
|
||||||
|
#include "net/inmessage.hpp"
|
||||||
|
|
||||||
class World;
|
class World;
|
||||||
|
|
||||||
namespace game::view
|
namespace game::view
|
||||||
@ -13,9 +18,12 @@ class EntityView
|
|||||||
public:
|
public:
|
||||||
EntityView(World& world) : world_(world) {}
|
EntityView(World& world) : world_(world) {}
|
||||||
|
|
||||||
virtual void Draw(gfx::DrawList& dlist) {}
|
virtual bool ProcessMsg( net::InMessage& msg) { return false; }
|
||||||
|
|
||||||
virtual void Update() {}
|
virtual void Update() {}
|
||||||
|
|
||||||
|
virtual void Draw(gfx::DrawList& dlist) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
World& world_;
|
World& world_;
|
||||||
TransformNode root_;
|
TransformNode root_;
|
||||||
|
|||||||
69
src/gameview/worldview.cpp
Normal file
69
src/gameview/worldview.cpp
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#include "worldview.hpp"
|
||||||
|
|
||||||
|
#include "assets/cache.hpp"
|
||||||
|
|
||||||
|
game::view::WorldView::WorldView()
|
||||||
|
{
|
||||||
|
map_ = assets::CacheManager::GetMap("data/openworld.map");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::view::WorldView::ProcessMsg(net::MessageType type, net::InMessage& msg)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case net::MSG_ENTSPAWN:
|
||||||
|
return ProcessEntSpawnMsg(msg);
|
||||||
|
|
||||||
|
case net::MSG_ENTMSG:
|
||||||
|
return ProcessEntMsgMsg(msg);
|
||||||
|
|
||||||
|
case net::MSG_ENTDESTROY:
|
||||||
|
return ProcessEntDestroyMsg(msg);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::view::WorldView::Draw(gfx::DrawList& dlist) const
|
||||||
|
{
|
||||||
|
if (map_)
|
||||||
|
map_->Draw(dlist);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::view::WorldView::ProcessEntMsgMsg(net::InMessage& msg)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::view::WorldView::ProcessEntDestroyMsg(net::InMessage& msg)
|
||||||
|
{
|
||||||
|
net::EntNum entnum;
|
||||||
|
if (!msg.Read(entnum))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ents_.erase(entnum);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@ -1,13 +1,34 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "assets/map.hpp"
|
||||||
|
#include "gfx/draw_list.hpp"
|
||||||
|
#include "net/defs.hpp"
|
||||||
|
#include "net/inmessage.hpp"
|
||||||
|
|
||||||
|
#include "entityview.hpp"
|
||||||
|
|
||||||
namespace game::view
|
namespace game::view
|
||||||
{
|
{
|
||||||
|
|
||||||
class WorldView
|
class WorldView
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
WorldView();
|
||||||
|
|
||||||
|
bool ProcessMsg(net::MessageType type, net::InMessage& msg);
|
||||||
|
|
||||||
|
void Draw(gfx::DrawList& dlist) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// msg handlers
|
||||||
|
bool ProcessEntSpawnMsg(net::InMessage& msg);
|
||||||
|
bool ProcessEntMsgMsg(net::InMessage& msg);
|
||||||
|
bool ProcessEntDestroyMsg(net::InMessage& msg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<const assets::Map> map_;
|
||||||
|
|
||||||
|
std::map<net::EntNum, std::unique_ptr<EntityView>> ents_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "assets/map.hpp"
|
|
||||||
#include "assets/skeleton.hpp"
|
#include "assets/skeleton.hpp"
|
||||||
#include "surface.hpp"
|
#include "surface.hpp"
|
||||||
|
|
||||||
@ -11,12 +10,12 @@ namespace gfx
|
|||||||
|
|
||||||
struct DrawSurfaceCmd
|
struct DrawSurfaceCmd
|
||||||
{
|
{
|
||||||
const Surface* surface;
|
const Surface* surface = nullptr;
|
||||||
const glm::mat4* matrices; // model matrix, continues in array of matrices for skeletal meshes
|
const glm::mat4* matrices = nullptr; // model matrix, continues in array of matrices for skeletal meshes
|
||||||
const glm::vec4* color; // optional tint
|
const glm::vec4* color = nullptr; // optional tint
|
||||||
uint32_t first; // first triangle index
|
uint32_t first = 0; // first triangle index
|
||||||
uint32_t count; // num triangles
|
uint32_t count = 0; // num triangles
|
||||||
float dist; // distance to camera - for transparnt sorting
|
float dist = 0.0f; // distance to camera - for transparnt sorting
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawList
|
struct DrawList
|
||||||
|
|||||||
@ -96,6 +96,8 @@ void gfx::Renderer::DrawSurfaceList(std::span<DrawSurfaceCmd> list, const DrawLi
|
|||||||
|
|
||||||
if (auto cmp = sa->va.get() <=> sb->va.get(); cmp != 0)
|
if (auto cmp = sa->va.get() <=> sb->va.get(); cmp != 0)
|
||||||
return cmp < 0;
|
return cmp < 0;
|
||||||
|
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
@ -140,7 +142,15 @@ void gfx::Renderer::DrawSurfaceList(std::span<DrawSurfaceCmd> list, const DrawLi
|
|||||||
SetupMeshShader(mshader, params);
|
SetupMeshShader(mshader, params);
|
||||||
|
|
||||||
// set model matrix
|
// set model matrix
|
||||||
glUniformMatrix4fv(mshader.shader->U(gfx::SU_MODEL), 1, GL_FALSE, &cmd.matrices[0][0][0]);
|
if (cmd.matrices)
|
||||||
|
{
|
||||||
|
glUniformMatrix4fv(mshader.shader->U(gfx::SU_MODEL), 1, GL_FALSE, &cmd.matrices[0][0][0]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // use identity if no matrix provided
|
||||||
|
static const glm::mat4 identity(1.0f);
|
||||||
|
glUniformMatrix4fv(mshader.shader->U(gfx::SU_MODEL), 1, GL_FALSE, &identity[0][0]);
|
||||||
|
}
|
||||||
|
|
||||||
// set color
|
// set color
|
||||||
glm::vec4 color = (object_color_flag && cmd.color) ? glm::vec4(*cmd.color) : glm::vec4(1.0f);
|
glm::vec4 color = (object_color_flag && cmd.color) ? glm::vec4(*cmd.color) : glm::vec4(1.0f);
|
||||||
|
|||||||
@ -85,7 +85,8 @@ void main() {
|
|||||||
gl_Position = u_view_proj * world_pos;
|
gl_Position = u_view_proj * world_pos;
|
||||||
|
|
||||||
v_uv = vec2(a_uv.x, 1.0 - a_uv.y);
|
v_uv = vec2(a_uv.x, 1.0 - a_uv.y);
|
||||||
v_color = ComputeLights(world_pos.xyz, world_normal) * a_color;
|
// v_color = ComputeLights(world_pos.xyz, world_normal) * a_color;
|
||||||
|
v_color = a_color;
|
||||||
}
|
}
|
||||||
)GLSL",
|
)GLSL",
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,10 @@ enum MessageType : uint8_t
|
|||||||
MSG_NONE,
|
MSG_NONE,
|
||||||
|
|
||||||
/*~~~~~~~~ Client->Server ~~~~~~~~*/
|
/*~~~~~~~~ Client->Server ~~~~~~~~*/
|
||||||
// IN <PlayerInputFlags> <ViewYaw> <ViewPitch>
|
// ID <PlayerName>
|
||||||
|
MSG_ID,
|
||||||
|
|
||||||
|
// IN <PlayerInputFlags> <ViewYawQ> <ViewPitchQ>
|
||||||
MSG_IN,
|
MSG_IN,
|
||||||
|
|
||||||
/*~~~~~~~~ World ~~~~~~~~*/
|
/*~~~~~~~~ World ~~~~~~~~*/
|
||||||
@ -26,17 +29,22 @@ enum MessageType : uint8_t
|
|||||||
MSG_ENTSPAWN,
|
MSG_ENTSPAWN,
|
||||||
// ENTMSG <EntNum> data...
|
// ENTMSG <EntNum> data...
|
||||||
MSG_ENTMSG,
|
MSG_ENTMSG,
|
||||||
// ENTRM <EntNum>
|
// ENTDESTROY <EntNum>
|
||||||
MSG_ENTRM,
|
MSG_ENTDESTROY,
|
||||||
|
|
||||||
/*~~~~~~~~~~~~~~~~*/
|
/*~~~~~~~~~~~~~~~~*/
|
||||||
MSG_COUNT,
|
MSG_COUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using PlayerName = FixedStr<24>;
|
||||||
using MapName = FixedStr<32>;
|
using MapName = FixedStr<32>;
|
||||||
|
|
||||||
using ViewYaw = Quantized<uint16_t, 0, 360>;
|
// pi approx fraction
|
||||||
using ViewPitch = Quantized<uint16_t, -180, 180>;
|
constexpr long long PI_N = 245850922;
|
||||||
|
constexpr long long PI_D = 78256779;
|
||||||
|
|
||||||
|
using ViewYawQ = Quantized<uint16_t, 0, 2 * PI_N, PI_D>;
|
||||||
|
using ViewPitchQ = Quantized<uint16_t, -PI_N, PI_N, PI_D>;
|
||||||
|
|
||||||
using EntNum = uint16_t;
|
using EntNum = uint16_t;
|
||||||
|
|
||||||
@ -44,10 +52,17 @@ enum EntType : uint8_t
|
|||||||
{
|
{
|
||||||
ET_NONE,
|
ET_NONE,
|
||||||
|
|
||||||
ET_PAWN,
|
ET_CHARACTER,
|
||||||
ET_CAR,
|
ET_VEHICLE,
|
||||||
|
|
||||||
ET_COUNT,
|
ET_COUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum EntMsgType : uint8_t
|
||||||
|
{
|
||||||
|
EMSG_NONE,
|
||||||
|
|
||||||
|
EMSG_UPDATE,
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace net
|
} // namespace net
|
||||||
@ -14,7 +14,12 @@ struct FixedStr
|
|||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
char str[N];
|
char str[N];
|
||||||
|
|
||||||
size_t MaxLen() const { return N; }
|
FixedStr() = default;
|
||||||
|
|
||||||
|
FixedStr(const std::string& stdstr)
|
||||||
|
{
|
||||||
|
*this = stdstr;
|
||||||
|
}
|
||||||
|
|
||||||
FixedStr& operator=(const std::string& stdstr)
|
FixedStr& operator=(const std::string& stdstr)
|
||||||
{
|
{
|
||||||
@ -23,6 +28,8 @@ struct FixedStr
|
|||||||
memcpy(str, stdstr.data(), putsize);
|
memcpy(str, stdstr.data(), putsize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t MaxLen() const { return N; }
|
||||||
|
|
||||||
operator std::string() { return std::string(str, len); }
|
operator std::string() { return std::string(str, len); }
|
||||||
operator std::string_view() { return std::string_view(str, len); }
|
operator std::string_view() { return std::string_view(str, len); }
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,6 +24,11 @@ public:
|
|||||||
return ptr_ + n <= end_;
|
return ptr_ + n <= end_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Eof() const
|
||||||
|
{
|
||||||
|
return ptr_ < end_;
|
||||||
|
}
|
||||||
|
|
||||||
bool Read(char* dest, size_t n)
|
bool Read(char* dest, size_t n)
|
||||||
{
|
{
|
||||||
if (!CheckAvail(n))
|
if (!CheckAvail(n))
|
||||||
@ -45,6 +50,17 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T> requires (std::is_enum_v<T> && std::integral<std::underlying_type_t<T>>)
|
||||||
|
bool Read(T& value)
|
||||||
|
{
|
||||||
|
std::underlying_type_t<T> und;
|
||||||
|
if (!Read(und))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
value = und;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
template <size_t N>
|
template <size_t N>
|
||||||
bool Read(FixedStr<N>& str)
|
bool Read(FixedStr<N>& str)
|
||||||
{
|
{
|
||||||
|
|||||||
16
src/net/msg_producer.cpp
Normal file
16
src/net/msg_producer.cpp
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#include "msg_producer.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
void net::MsgProducer::ResetMsg()
|
||||||
|
{
|
||||||
|
message_buf_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
net::OutMessage net::MsgProducer::BeginMsg(net::MessageType type)
|
||||||
|
{
|
||||||
|
OutMessage msg(message_buf_);
|
||||||
|
if (type != net::MSG_NONE)
|
||||||
|
msg.Write(type);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
24
src/net/msg_producer.hpp
Normal file
24
src/net/msg_producer.hpp
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include "defs.hpp"
|
||||||
|
#include "outmessage.hpp"
|
||||||
|
|
||||||
|
namespace net
|
||||||
|
{
|
||||||
|
|
||||||
|
class MsgProducer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MsgProducer() = default;
|
||||||
|
|
||||||
|
void ResetMsg();
|
||||||
|
OutMessage BeginMsg(MessageType type = MSG_NONE);
|
||||||
|
std::span<const char> GetMsg() const { return message_buf_; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<char> message_buf_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
#include "fixed_str.hpp"
|
#include "fixed_str.hpp"
|
||||||
#include "quantized.hpp"
|
#include "quantized.hpp"
|
||||||
@ -34,6 +35,12 @@ public:
|
|||||||
WriteAt(Reserve<T>(), value);
|
WriteAt(Reserve<T>(), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T> requires (std::is_enum_v<T> && std::integral<std::underlying_type_t<T>>)
|
||||||
|
void Write(T value)
|
||||||
|
{
|
||||||
|
Write(static_cast<std::underlying_type_t<T>>(value));
|
||||||
|
}
|
||||||
|
|
||||||
void Write(const char* str, size_t n)
|
void Write(const char* str, size_t n)
|
||||||
{
|
{
|
||||||
size_t pos = buffer_.size();
|
size_t pos = buffer_.size();
|
||||||
@ -41,6 +48,11 @@ public:
|
|||||||
memcpy(&buffer_[pos], str, n);
|
memcpy(&buffer_[pos], str, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Write(std::span<const char> data)
|
||||||
|
{
|
||||||
|
Write(data.data(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
template <size_t N>
|
template <size_t N>
|
||||||
void Write(const FixedStr<N>& str)
|
void Write(const FixedStr<N>& str)
|
||||||
{
|
{
|
||||||
|
|||||||
62
src/server/client.cpp
Normal file
62
src/server/client.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#include "client.hpp"
|
||||||
|
|
||||||
|
#include "server.hpp"
|
||||||
|
#include "utils/validate.hpp"
|
||||||
|
|
||||||
|
sv::Client::Client(Server& server, WSConnId id) : server_(server), id_(id) {}
|
||||||
|
|
||||||
|
bool sv::Client::ProcessMessage(net::InMessage& msg)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
net::MessageType type = net::MSG_NONE;
|
||||||
|
if (!msg.Read(type))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (type == net::MSG_NONE || type >= net::MSG_COUNT)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ProcessSingleMessage(type, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sv::Client::ProcessSingleMessage(net::MessageType type, net::InMessage& msg)
|
||||||
|
{
|
||||||
|
if (state_ == CS_PLAYER)
|
||||||
|
return player_->ProcessMsg(type, msg);
|
||||||
|
|
||||||
|
if (state_ == CS_INIT)
|
||||||
|
{
|
||||||
|
// allow only login
|
||||||
|
if (type != net::MSG_ID)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
net::PlayerName name;
|
||||||
|
if (!msg.Read(name) || !utils::IsAlphanumeric(name))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
player_ = std::make_unique<game::Player>(server_.GetGame(), name);
|
||||||
|
state_ = CS_PLAYER;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sv::Client::Update()
|
||||||
|
{
|
||||||
|
if (player_)
|
||||||
|
{
|
||||||
|
player_->ResetMsg();
|
||||||
|
player_->Update();
|
||||||
|
|
||||||
|
auto msg = player_->GetMsg();
|
||||||
|
if (!msg.empty())
|
||||||
|
Send(std::string(msg.data(), msg.size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sv::Client::Send(std::string msg)
|
||||||
|
{
|
||||||
|
server_.Send(*this, std::move(msg));
|
||||||
|
}
|
||||||
48
src/server/client.hpp
Normal file
48
src/server/client.hpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wsserver.hpp"
|
||||||
|
#include "net/inmessage.hpp"
|
||||||
|
#include "game/player.hpp"
|
||||||
|
|
||||||
|
namespace sv
|
||||||
|
{
|
||||||
|
|
||||||
|
class Server;
|
||||||
|
|
||||||
|
enum ClientState
|
||||||
|
{
|
||||||
|
CS_INIT,
|
||||||
|
CS_PLAYER,
|
||||||
|
CS_CLOSED,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Client
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Client(Server& server, WSConnId id);
|
||||||
|
|
||||||
|
bool ProcessMessage(net::InMessage& msg);
|
||||||
|
bool ProcessSingleMessage(net::MessageType type, net::InMessage& msg);
|
||||||
|
|
||||||
|
void Update();
|
||||||
|
|
||||||
|
// void Disconnect(const std::string& reason);
|
||||||
|
|
||||||
|
WSConnId GetConnId() const { return id_; }
|
||||||
|
ClientState GetState() const { return state_; }
|
||||||
|
|
||||||
|
game::Player* GetPlayer() { return player_.get(); }
|
||||||
|
const game::Player* GetPlayer() const { return player_.get(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Send(std::string msg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Server& server_;
|
||||||
|
WSConnId id_ = 0;
|
||||||
|
ClientState state_ = CS_INIT;
|
||||||
|
|
||||||
|
std::unique_ptr<game::Player> player_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
19
src/server/main.cpp
Normal file
19
src/server/main.cpp
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "server.hpp"
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sv::Server server(11200);
|
||||||
|
server.Run();
|
||||||
|
} catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
std::cerr << "FATAL ERROR: " << e.what() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
77
src/server/server.cpp
Normal file
77
src/server/server.cpp
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#include "server.hpp"
|
||||||
|
|
||||||
|
sv::Server::Server(uint16_t port) : ws_(port) {}
|
||||||
|
|
||||||
|
void sv::Server::Run()
|
||||||
|
{
|
||||||
|
bool exit = false;
|
||||||
|
while (!exit)
|
||||||
|
{
|
||||||
|
PollWSEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sv::Server::Send(Client& client, std::string msg)
|
||||||
|
{
|
||||||
|
ws_.Send(client.GetConnId(), std::move(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
void sv::Server::PollWSEvents()
|
||||||
|
{
|
||||||
|
WSEvent event;
|
||||||
|
while (ws_.PollEvent(event))
|
||||||
|
{
|
||||||
|
switch (event.type)
|
||||||
|
{
|
||||||
|
case WSE_CONNECTED:
|
||||||
|
HandleWSConnect(event.conn);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WSE_MESSAGE:
|
||||||
|
HandleWSMessage(event.conn, event.data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WSE_DISCONNECTED:
|
||||||
|
HandleWSDisconnect(event.conn);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WSE_EXIT:
|
||||||
|
exit_ = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sv::Server::HandleWSConnect(WSConnId conn)
|
||||||
|
{
|
||||||
|
clients_[conn] = std::make_unique<Client>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sv::Server::HandleWSMessage(WSConnId conn, const std::string& data)
|
||||||
|
{
|
||||||
|
net::InMessage msg(data.data(), data.size());
|
||||||
|
if (!clients_.at(conn)->ProcessMessage(msg))
|
||||||
|
{
|
||||||
|
// TODO: disconnect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sv::Server::HandleWSDisconnect(WSConnId conn)
|
||||||
|
{
|
||||||
|
clients_.erase(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sv::Server::Update()
|
||||||
|
{
|
||||||
|
// update game
|
||||||
|
|
||||||
|
|
||||||
|
// update players
|
||||||
|
for (const auto& [conn, client] : clients_)
|
||||||
|
{
|
||||||
|
client->Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/server/server.hpp
Normal file
44
src/server/server.hpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "wsserver.hpp"
|
||||||
|
#include "client.hpp"
|
||||||
|
|
||||||
|
#include "game/game.hpp"
|
||||||
|
|
||||||
|
namespace sv
|
||||||
|
{
|
||||||
|
|
||||||
|
class Server
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Server(uint16_t port);
|
||||||
|
|
||||||
|
void Run();
|
||||||
|
|
||||||
|
void Send(Client& client, std::string msg);
|
||||||
|
|
||||||
|
game::Game& GetGame() { return game_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void PollWSEvents();
|
||||||
|
void HandleWSConnect(WSConnId conn);
|
||||||
|
void HandleWSMessage(WSConnId conn, const std::string& data);
|
||||||
|
void HandleWSDisconnect(WSConnId conn);
|
||||||
|
|
||||||
|
void Update();
|
||||||
|
|
||||||
|
private:
|
||||||
|
WSServer ws_;
|
||||||
|
bool exit_ = false;
|
||||||
|
|
||||||
|
game::Game game_;
|
||||||
|
std::unordered_map<WSConnId, std::unique_ptr<Client>> clients_;
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
107
src/server/wsserver.cpp
Normal file
107
src/server/wsserver.cpp
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
#include "wsserver.hpp"
|
||||||
|
|
||||||
|
#include <crow.h>
|
||||||
|
|
||||||
|
#include "utils/allocnum.hpp"
|
||||||
|
|
||||||
|
sv::WSServer::WSServer(uint16_t port)
|
||||||
|
{
|
||||||
|
ws_thread_ = std::make_unique<std::thread>([=]() {
|
||||||
|
crow::SimpleApp app;
|
||||||
|
app_ptr_ = (void*)&app;
|
||||||
|
|
||||||
|
CROW_WEBSOCKET_ROUTE(app, "/ws")
|
||||||
|
.onopen([&](crow::websocket::connection& conn) {
|
||||||
|
CROW_LOG_INFO << "new websocket connection from " << conn.get_remote_ip();
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
|
|
||||||
|
WSConnId conn_id = utils::AllocNum(id2conn_, last_id_);
|
||||||
|
|
||||||
|
// register connection
|
||||||
|
id2conn_[conn_id] = &conn;
|
||||||
|
conn.userdata((void*)conn_id);
|
||||||
|
// push connection event
|
||||||
|
events_.emplace_back(WSE_CONNECTED, conn_id);
|
||||||
|
})
|
||||||
|
.onclose([&](crow::websocket::connection& conn, const std::string& reason, uint16_t) {
|
||||||
|
CROW_LOG_INFO << "websocket connection closed: " << reason;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
|
|
||||||
|
WSConnId conn_id = (WSConnId)conn.userdata();
|
||||||
|
|
||||||
|
// push disonnected event
|
||||||
|
events_.emplace_back(WSE_DISCONNECTED, conn_id);
|
||||||
|
|
||||||
|
id2conn_.erase(conn_id);
|
||||||
|
})
|
||||||
|
.onmessage([&](crow::websocket::connection& conn, const std::string& data, bool is_binary) {
|
||||||
|
if (!is_binary)
|
||||||
|
return; // only accept binary messages here
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
|
|
||||||
|
WSConnId conn_id = (WSConnId)conn.userdata();
|
||||||
|
events_.emplace_back(WSE_MESSAGE, conn_id, data);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// CROW_ROUTE(app, "/")
|
||||||
|
// ([] {
|
||||||
|
// crow::mustache::context x;
|
||||||
|
// x["servername"] = "127.0.0.1";
|
||||||
|
// auto page = crow::mustache::load("ws.html");
|
||||||
|
// return page.render(x);
|
||||||
|
// });
|
||||||
|
|
||||||
|
app.port(port).run();
|
||||||
|
|
||||||
|
// push exit event
|
||||||
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
|
events_.emplace_back(WSE_EXIT);
|
||||||
|
|
||||||
|
app_ptr_ = nullptr;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sv::WSServer::PollEvent(WSEvent &out_event)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
|
|
||||||
|
if (events_.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
out_event = std::move(events_.front());
|
||||||
|
events_.pop_front();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sv::WSServer::Send(WSConnId conn_id, std::string data)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
|
|
||||||
|
auto it = id2conn_.find(conn_id);
|
||||||
|
if (it == id2conn_.end())
|
||||||
|
{
|
||||||
|
std::cerr << "attempted to send message to unknown conn ID " << conn_id <<std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*it->second).send_binary(std::move(data));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void sv::WSServer::Exit()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
|
if (app_ptr_)
|
||||||
|
((crow::SimpleApp*)app_ptr_)->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
sv::WSServer::~WSServer()
|
||||||
|
{
|
||||||
|
if (ws_thread_ && ws_thread_->joinable())
|
||||||
|
ws_thread_->join();
|
||||||
|
}
|
||||||
69
src/server/wsserver.hpp
Normal file
69
src/server/wsserver.hpp
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
#include <deque>
|
||||||
|
#include <mutex>
|
||||||
|
#include <functional>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace crow::websocket
|
||||||
|
{
|
||||||
|
class connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace sv
|
||||||
|
{
|
||||||
|
|
||||||
|
enum WSEventType
|
||||||
|
{
|
||||||
|
WSE_NONE,
|
||||||
|
WSE_EXIT,
|
||||||
|
WSE_CONNECTED,
|
||||||
|
WSE_MESSAGE,
|
||||||
|
WSE_DISCONNECTED,
|
||||||
|
};
|
||||||
|
|
||||||
|
using WSConnId = uint32_t;
|
||||||
|
|
||||||
|
struct WSEvent
|
||||||
|
{
|
||||||
|
WSEventType type = WSE_NONE;
|
||||||
|
WSConnId conn = 0;
|
||||||
|
std::string data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// using WSEventHandler = std::function<void(const WSEvent&)>;
|
||||||
|
|
||||||
|
class WSServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WSServer(uint16_t port);
|
||||||
|
|
||||||
|
WSServer(const WSServer& other) = delete;
|
||||||
|
WSServer(WSServer&& other) = delete;
|
||||||
|
WSServer& operator=(const WSServer& other) = delete;
|
||||||
|
WSServer& operator=(WSServer&& other) = delete;
|
||||||
|
|
||||||
|
bool PollEvent(WSEvent& out_event);
|
||||||
|
void Send(WSConnId conn_id, std::string data);
|
||||||
|
// void Close(WSConnId conn_id);
|
||||||
|
|
||||||
|
void Exit();
|
||||||
|
|
||||||
|
~WSServer();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::deque<WSEvent> events_;
|
||||||
|
std::mutex mtx_;
|
||||||
|
|
||||||
|
std::unique_ptr<std::thread> ws_thread_;
|
||||||
|
void* app_ptr_ = nullptr;
|
||||||
|
|
||||||
|
std::unordered_map<WSConnId, crow::websocket::connection*> id2conn_;
|
||||||
|
WSConnId last_id_ = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
24
src/utils/allocnum.hpp
Normal file
24
src/utils/allocnum.hpp
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
|
|
||||||
|
namespace utils
|
||||||
|
{
|
||||||
|
|
||||||
|
template <std::unsigned_integral TNum, typename TMap>
|
||||||
|
TNum AllocNum(const TMap& map, TNum& num)
|
||||||
|
{
|
||||||
|
constexpr auto MAX_NUM = std::numeric_limits<TNum>().max();
|
||||||
|
if (ents_.size() >= MAX_NUM - 2) // 0 & MAX reserved
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// this is stupid but whatever
|
||||||
|
do
|
||||||
|
{
|
||||||
|
++num;
|
||||||
|
} while (num == 0 || num == MAX_ENTNUM || ents_.find(num) != ents_.end());
|
||||||
|
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
src/utils/defs.hpp
Normal file
13
src/utils/defs.hpp
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef SERVER
|
||||||
|
#define SERVER_ONLY(...) __VA_ARGS__
|
||||||
|
#else
|
||||||
|
#define SERVER_ONLY(...)
|
||||||
|
#endif // SERVER
|
||||||
|
|
||||||
|
#ifdef CLIENT
|
||||||
|
#define CLIENT_ONLY(...) __VA_ARGS__
|
||||||
|
#else
|
||||||
|
#define CLIENT_ONLY(...)
|
||||||
|
#endif
|
||||||
@ -3,6 +3,8 @@
|
|||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/quaternion.hpp>
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
|
||||||
|
#include <btBulletCollisionCommon.h>
|
||||||
|
|
||||||
struct Transform
|
struct Transform
|
||||||
{
|
{
|
||||||
glm::vec3 position = glm::vec3(0.0f);
|
glm::vec3 position = glm::vec3(0.0f);
|
||||||
@ -41,12 +43,27 @@ struct Transform
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
btTransform ToBtTransform() const
|
||||||
|
{
|
||||||
|
btQuaternion bt_rotation(rotation.x, rotation.y, rotation.z, rotation.w);
|
||||||
|
btVector3 bt_position(position.x, position.y, position.z);
|
||||||
|
return btTransform(bt_rotation, bt_position);
|
||||||
|
}
|
||||||
|
|
||||||
void SetAngles(const glm::vec3& angles_deg)
|
void SetAngles(const glm::vec3& angles_deg)
|
||||||
{
|
{
|
||||||
glm::vec3 angles_rad = glm::radians(angles_deg);
|
glm::vec3 angles_rad = glm::radians(angles_deg);
|
||||||
rotation = glm::quat(angles_rad);
|
rotation = glm::quat(angles_rad);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetBtTransform(const btTransform& bt_trans)
|
||||||
|
{
|
||||||
|
btVector3 bt_position = bt_trans.getOrigin();
|
||||||
|
btQuaternion bt_rotation = bt_trans.getRotation();
|
||||||
|
position = glm::vec3(bt_position.x(), bt_position.y(), bt_position.z());
|
||||||
|
rotation = glm::quat(bt_rotation.w(), bt_rotation.x(), bt_rotation.y(), bt_rotation.z());
|
||||||
|
}
|
||||||
|
|
||||||
static Transform Lerp(const Transform& a, const Transform& b, float t)
|
static Transform Lerp(const Transform& a, const Transform& b, float t)
|
||||||
{
|
{
|
||||||
Transform result;
|
Transform result;
|
||||||
|
|||||||
34
src/utils/validate.hpp
Normal file
34
src/utils/validate.hpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include "net/fixed_str.hpp"
|
||||||
|
|
||||||
|
namespace utils
|
||||||
|
{
|
||||||
|
|
||||||
|
inline bool IsAlphanumeric(const char* str, size_t len)
|
||||||
|
{
|
||||||
|
const char* end = str + len;
|
||||||
|
for (; str < end; ++str)
|
||||||
|
{
|
||||||
|
const char c = *str;
|
||||||
|
if (!((c >= 'a' && c <= 'z') || (c >= 'A' || c <= 'Z') || (c >= '0' && c <= '9') || c == '_'))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsAlphanumeric(std::string_view str)
|
||||||
|
{
|
||||||
|
return IsAlphanumeric(str.data(), str.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
inline bool IsAlphanumeric(const net::FixedStr<N>& str)
|
||||||
|
{
|
||||||
|
return IsAlphanumeric(str.str, str.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user