Compare commits
15 Commits
alpha-0.0.
...
582a66d4eb
| Author | SHA1 | Date | |
|---|---|---|---|
| 582a66d4eb | |||
| 07d09332dd | |||
| 747fa13d3a | |||
| 9a6642b1ad | |||
| 32ca078002 | |||
| d823dfa9d7 | |||
| 028715d3b5 | |||
| d4902214ae | |||
| e1efc5065c | |||
| 8795562b42 | |||
| 048812090b | |||
| ad71bbbdf7 | |||
| 0bb7d28da8 | |||
| 8e13bac9d1 | |||
| 76b3057271 |
@@ -10,7 +10,7 @@ jobs:
|
||||
- name: Install deps
|
||||
run : |
|
||||
apt update
|
||||
apt install -y libsdl2-dev libassimp-dev libglew-dev
|
||||
apt install -y libsdl2-dev libassimp-dev libglew-dev
|
||||
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
@@ -22,11 +22,19 @@ jobs:
|
||||
actions-cache-folder: '.xmake-cache'
|
||||
actions-cache-key: 'ubuntu'
|
||||
|
||||
- name: Calc deps hash
|
||||
uses: seepine/hash-files@v1
|
||||
id: get-hash
|
||||
with:
|
||||
patterns: |
|
||||
**/xmake.lua
|
||||
**/xmake/*.lua
|
||||
|
||||
- name: Packages cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.xmake
|
||||
key: 'ubuntu-packages'
|
||||
key: ${{ runner.os }}-${{ steps.get-hash.outputs.hash }}
|
||||
|
||||
- name: XMake config
|
||||
run: xmake f -p linux -y --root
|
||||
@@ -35,4 +43,4 @@ jobs:
|
||||
run: xmake --root
|
||||
|
||||
- name: Test
|
||||
run: xmake test --root
|
||||
run: xmake test --root
|
||||
BIN
assets/sessionD.wav
Normal file
BIN
assets/sessionD.wav
Normal file
Binary file not shown.
@@ -158,6 +158,14 @@ class DataBuffer {
|
||||
return m_Buffer.data();
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t>& GetVector() {
|
||||
return m_Buffer;
|
||||
}
|
||||
|
||||
const std::vector<std::uint8_t>& GetVector() const {
|
||||
return m_Buffer;
|
||||
}
|
||||
|
||||
std::size_t GetReadOffset() const {
|
||||
return m_ReadOffset;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ class ClientListener {
|
||||
virtual void OnSpectatorChange(game::PlayerID player) {}
|
||||
virtual void OnPlayerShoot(PlayerID player, const Vec3f& position, float yaw, float pitch) {}
|
||||
virtual void OnGameConfigUpdate() {}
|
||||
virtual void OnGameJoin() {}
|
||||
virtual void OnGameLeave() {}
|
||||
};
|
||||
|
||||
} // namespace game
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
|
||||
@@ -41,28 +41,28 @@ class Connexion : public protocol::PacketHandler, private NonCopyable {
|
||||
|
||||
/**
|
||||
* \brief Default destructor
|
||||
*/
|
||||
*/
|
||||
virtual ~Connexion();
|
||||
|
||||
/**
|
||||
* \brief Reads socket and process a Packet, if any
|
||||
*/
|
||||
* \brief Reads socket and process a Packet, if any
|
||||
*/
|
||||
virtual bool UpdateSocket();
|
||||
|
||||
|
||||
/**
|
||||
* \brief Closes the connexion
|
||||
*/
|
||||
*/
|
||||
void CloseConnection();
|
||||
|
||||
/**
|
||||
* \brief Tries to connect the socket at the specified address and port
|
||||
* \return Wether this action was succesfull
|
||||
*/
|
||||
virtual bool Connect(const std::string& address, std::uint16_t port);
|
||||
*/
|
||||
bool Connect(const std::string& address, std::uint16_t port);
|
||||
|
||||
/**
|
||||
* \brief Returns the TCPSocket::Status of the internal socket
|
||||
*/
|
||||
*/
|
||||
TCPSocket::Status GetSocketStatus() const {
|
||||
return m_Socket.GetStatus();
|
||||
}
|
||||
@@ -70,7 +70,7 @@ class Connexion : public protocol::PacketHandler, private NonCopyable {
|
||||
/**
|
||||
* \brief Sends the protocol::Packet over the network to the remote
|
||||
* \param packet The protocol::Packet to send
|
||||
*/
|
||||
*/
|
||||
void SendPacket(const protocol::Packet* packet);
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class ClientConnexion : public network::Connexion {
|
||||
virtual void HandlePacket(const protocol::ChatPacket* packet) override;
|
||||
virtual void HandlePacket(const protocol::ConnexionInfoPacket* packet) override;
|
||||
|
||||
virtual bool Connect(const std::string& pseudo, const std::string& address, std::uint16_t port);
|
||||
bool Connect(const std::string& pseudo, const std::string& address, std::uint16_t port);
|
||||
|
||||
game::PlayerID GetPlayerID() const {
|
||||
return m_PlayerID;
|
||||
|
||||
25
include/client/audio/AudioBuffer.h
Normal file
25
include/client/audio/AudioBuffer.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "blitz/common/DataBuffer.h"
|
||||
#include "blitz/common/NonCopyable.h"
|
||||
#include "blitz/maths/Vector.h"
|
||||
#include "client/audio/AudioLoader.h"
|
||||
|
||||
namespace blitz {
|
||||
namespace audio {
|
||||
|
||||
class AudioBuffer : private NonCopyable {
|
||||
private:
|
||||
unsigned int m_ID;
|
||||
|
||||
public:
|
||||
AudioBuffer(AudioData&& audioData);
|
||||
~AudioBuffer();
|
||||
|
||||
unsigned int GetID() const {
|
||||
return m_ID;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace audio
|
||||
} // namespace blitz
|
||||
21
include/client/audio/AudioListener.h
Normal file
21
include/client/audio/AudioListener.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "blitz/common/NonCopyable.h"
|
||||
#include "blitz/maths/Vector.h"
|
||||
|
||||
namespace blitz {
|
||||
namespace audio {
|
||||
|
||||
class AudioListener : private NonCopyable {
|
||||
public:
|
||||
AudioListener();
|
||||
~AudioListener();
|
||||
|
||||
void SetGain(float a_Gain);
|
||||
void SetPosition(const Vec3f& a_Position);
|
||||
void SetVelocity(const Vec3f& a_Velocity);
|
||||
void SetOrientation(const Vec3f& a_Orientation, const Vec3f& a_Up);
|
||||
};
|
||||
|
||||
} // namespace audio
|
||||
} // namespace blitz
|
||||
24
include/client/audio/AudioLoader.h
Normal file
24
include/client/audio/AudioLoader.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "blitz/common/DataBuffer.h"
|
||||
|
||||
namespace blitz {
|
||||
namespace audio {
|
||||
|
||||
enum AudioFormat { afMono8 = 0, afMono16, afStereo8, afStereo16 };
|
||||
|
||||
struct AudioData {
|
||||
DataBuffer m_Buffer;
|
||||
AudioFormat m_Format;
|
||||
std::uint32_t m_Frequency;
|
||||
};
|
||||
|
||||
namespace AudioLoader {
|
||||
|
||||
// files should be 8 or 16 bits wav
|
||||
AudioData LoadAudioFile(const std::string& filePath);
|
||||
|
||||
}; // namespace AudioLoader
|
||||
|
||||
} // namespace audio
|
||||
} // namespace blitz
|
||||
33
include/client/audio/AudioManager.h
Normal file
33
include/client/audio/AudioManager.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "blitz/game/Listeners.h"
|
||||
#include "client/audio/AudioListener.h"
|
||||
#include "client/audio/AudioSource.h"
|
||||
|
||||
namespace blitz {
|
||||
|
||||
class Client;
|
||||
|
||||
namespace audio {
|
||||
|
||||
class AudioManager : public game::ClientListener {
|
||||
private:
|
||||
Client* m_Client;
|
||||
AudioListener m_Listener;
|
||||
AudioSourcePtr m_MenuMusic;
|
||||
std::vector<AudioSourcePtr> m_Sources;
|
||||
|
||||
public:
|
||||
AudioManager(Client* client);
|
||||
~AudioManager();
|
||||
|
||||
virtual void OnGameJoin() override;
|
||||
virtual void OnGameLeave() override;
|
||||
|
||||
private:
|
||||
void InitMainMenuMusic();
|
||||
};
|
||||
|
||||
} // namespace audio
|
||||
|
||||
} // namespace blitz
|
||||
42
include/client/audio/AudioSource.h
Normal file
42
include/client/audio/AudioSource.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "blitz/common/NonCopyable.h"
|
||||
#include "blitz/maths/Vector.h"
|
||||
#include "client/audio/AudioBuffer.h"
|
||||
#include <memory>
|
||||
|
||||
namespace blitz {
|
||||
namespace audio {
|
||||
|
||||
typedef std::unique_ptr<AudioBuffer> AudioBufferPtr;
|
||||
|
||||
class AudioSource : NonCopyable {
|
||||
private:
|
||||
unsigned int m_ID;
|
||||
AudioBufferPtr m_Buffer;
|
||||
|
||||
public:
|
||||
enum SourceState { ssInitial = 0, ssPlaying, ssPaused, ssStopped };
|
||||
|
||||
AudioSource();
|
||||
~AudioSource();
|
||||
|
||||
void SetGain(float a_Gain);
|
||||
void SetPitch(float a_Pitch);
|
||||
void SetPosition(const Vec3f& a_Position);
|
||||
void SetDirection(const Vec3f& a_Direction);
|
||||
void SetVelocity(const Vec3f& a_Velocity);
|
||||
void SetLooping(bool a_Looping);
|
||||
void SetBuffer(AudioBufferPtr&& a_Buffer);
|
||||
|
||||
SourceState GetSourceState() const;
|
||||
|
||||
void Play();
|
||||
void Pause();
|
||||
void Stop();
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<AudioSource> AudioSourcePtr;
|
||||
|
||||
} // namespace audio
|
||||
} // namespace blitz
|
||||
@@ -19,7 +19,7 @@ typedef std::array<int, kaMax> Keybinds;
|
||||
|
||||
class BlitzConfig {
|
||||
private:
|
||||
std::array<char, 256> m_Pseudo;
|
||||
std::array<char, 20> m_Pseudo;
|
||||
game::GameConfig m_ServerConfig;
|
||||
bool m_VSync;
|
||||
bool m_DisplayFps;
|
||||
@@ -30,7 +30,7 @@ class BlitzConfig {
|
||||
BlitzConfig();
|
||||
~BlitzConfig();
|
||||
|
||||
std::array<char, 256>& GetPseudo() {
|
||||
std::array<char, 20>& GetPseudo() {
|
||||
return m_Pseudo;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ class ClientGame : public game::Game, public protocol::PacketHandler {
|
||||
virtual void HandlePacket(const protocol::UpdateGameStatePacket* packet) override;
|
||||
virtual void HandlePacket(const protocol::ServerConfigPacket* packet) override;
|
||||
|
||||
virtual void AddPlayer(game::PlayerID player, const std::string& name);
|
||||
virtual void RemovePlayer(game::PlayerID player);
|
||||
virtual void AddPlayer(game::PlayerID player, const std::string& name) override;
|
||||
virtual void RemovePlayer(game::PlayerID player) override;
|
||||
|
||||
const game::LeaderBoard& GetLeaderBoard() const {
|
||||
return m_LeaderBoard;
|
||||
|
||||
@@ -26,7 +26,8 @@ class GameChatGui : public GuiWidget, game::ClientListener {
|
||||
|
||||
public:
|
||||
GameChatGui(GuiWidget* parent, Client* client);
|
||||
virtual void OnTextChatReceived(const protocol::ColoredText& text);
|
||||
|
||||
virtual void OnTextChatReceived(const protocol::ColoredText& text) override;
|
||||
|
||||
virtual void Render() override;
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ class Client;
|
||||
|
||||
namespace game {
|
||||
class Player;
|
||||
} // namespace game
|
||||
} // namespace game
|
||||
|
||||
namespace gui {
|
||||
class Hud : public GuiWidget {
|
||||
|
||||
@@ -28,7 +28,6 @@ class BulletRenderer {
|
||||
std::vector<Trail> m_Trails;
|
||||
ModelLoader::Model m_BulletModel;
|
||||
std::unique_ptr<shader::BulletShader> m_Shader;
|
||||
unsigned int m_Vbo;
|
||||
const Camera& m_Camera;
|
||||
|
||||
public:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef BLITZ_GL_LOADER_GLBNIDING
|
||||
#ifdef BLITZ_GL_LOADER_GLEW
|
||||
#include <GL/glew.h>
|
||||
#else
|
||||
#include <glbinding/gl/gl.h>
|
||||
|
||||
@@ -38,6 +38,8 @@ class Server {
|
||||
|
||||
void AddBot();
|
||||
|
||||
void KickPlayer(game::PlayerID player);
|
||||
|
||||
void RemoveConnexion(std::uint8_t connexionID);
|
||||
|
||||
void BroadcastPacket(const protocol::Packet* packet);
|
||||
|
||||
@@ -48,7 +48,7 @@ class ServerConnexion : public network::Connexion {
|
||||
return m_ID;
|
||||
}
|
||||
|
||||
virtual bool UpdateSocket();
|
||||
virtual bool UpdateSocket() override;
|
||||
|
||||
private:
|
||||
void RegisterHandlers();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "blitz/network/Network.h"
|
||||
#include "client/Client.h"
|
||||
#include "client/audio/AudioManager.h"
|
||||
#include "client/display/Display.h"
|
||||
#include "client/render/MainRenderer.h"
|
||||
|
||||
@@ -12,6 +13,7 @@ int main(int argc, char** argv) {
|
||||
return EXIT_FAILURE;
|
||||
|
||||
blitz::render::MainRenderer renderer(&client);
|
||||
blitz::audio::AudioManager audioManager(&client);
|
||||
blitz::network::NetworkInitializer network;
|
||||
|
||||
while (!display.IsCloseRequested()) {
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
#include "blitz/network/Network.h"
|
||||
|
||||
#include "blitz/misc/Log.h"
|
||||
#include <signal.h>
|
||||
|
||||
|
||||
namespace blitz {
|
||||
namespace network {
|
||||
|
||||
/* Catch Signal Handler function */
|
||||
void signal_callback_handler(int signum) {
|
||||
utils::LOGD("[Network] Caught a SIGPIPE !");
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
NetworkInitializer::NetworkInitializer() {
|
||||
WSADATA wsaData;
|
||||
@@ -18,7 +25,10 @@ NetworkInitializer::~NetworkInitializer() {
|
||||
WSACleanup();
|
||||
}
|
||||
#else
|
||||
NetworkInitializer::NetworkInitializer() {}
|
||||
NetworkInitializer::NetworkInitializer() {
|
||||
/* Prevents the game for crashing when socket closes on the other side */
|
||||
signal(SIGPIPE, signal_callback_handler);
|
||||
}
|
||||
NetworkInitializer::~NetworkInitializer() {}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -121,13 +121,15 @@ size_t TCPSocket::Send(const unsigned char* data, size_t size) {
|
||||
|
||||
while (sent < size) {
|
||||
int cur = send(m_Handle, reinterpret_cast<const char*>(data + sent), static_cast<int>(size - sent), 0);
|
||||
|
||||
if (cur <= 0) {
|
||||
m_Status = Status::Error;
|
||||
Disconnect();
|
||||
m_Status = Status::Error;
|
||||
return 0;
|
||||
}
|
||||
sent += static_cast<std::size_t>(cur);
|
||||
}
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
@@ -149,6 +151,7 @@ std::size_t TCPSocket::Receive(DataBuffer& buffer, std::size_t amount) {
|
||||
|
||||
Disconnect();
|
||||
buffer.Clear();
|
||||
m_Status = Status::Error;
|
||||
return 0;
|
||||
}
|
||||
buffer.Resize(static_cast<std::size_t>(recvAmount));
|
||||
|
||||
@@ -13,7 +13,7 @@ DataBuffer GetAsset(const std::string& fileName) {
|
||||
|
||||
SDL_RWops* io = SDL_RWFromFile(fileName.c_str(), "rb");
|
||||
|
||||
assert(io != nullptr);
|
||||
assert(io != nullptr && "Can't find the requested file !");
|
||||
|
||||
std::int64_t bufferSize = SDL_RWsize(io);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "client/Client.h"
|
||||
|
||||
#include "blitz/misc/Log.h"
|
||||
#include "blitz/protocol/packets/ChatPacket.h"
|
||||
#include "blitz/protocol/packets/DisconnectPacket.h"
|
||||
#include "blitz/protocol/packets/PlayerPositionAndRotationPacket.h"
|
||||
@@ -17,11 +18,13 @@ Client::Client() :
|
||||
m_Game(std::make_unique<client::ClientGame>(this, m_Connexion->GetDispatcher())) {}
|
||||
|
||||
Client::~Client() {
|
||||
protocol::DisconnectPacket packet("Client");
|
||||
m_Connexion->SendPacket(&packet);
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
void Client::Update() {
|
||||
if (m_Connexion->GetSocketStatus() != network::TCPSocket::Status::Connected)
|
||||
return;
|
||||
|
||||
if (m_Connexion->UpdateSocket()) {
|
||||
static std::uint64_t lastTime = utils::GetTime();
|
||||
|
||||
@@ -29,6 +32,8 @@ void Client::Update() {
|
||||
m_Game->Tick(utils::GetTime() - lastTime);
|
||||
|
||||
lastTime = utils::GetTime();
|
||||
} else {
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,11 +62,21 @@ bool Client::CreateGame(std::uint16_t port, const std::string& pseudo) {
|
||||
}
|
||||
|
||||
void Client::Disconnect() {
|
||||
if (m_Connexion->GetSocketStatus() != network::TCPSocket::Status::Connected)
|
||||
return;
|
||||
|
||||
protocol::DisconnectPacket packet("Client wants to leave !");
|
||||
m_Connexion->SendPacket(&packet);
|
||||
|
||||
utils::LOGD("[Client] Disconnected !");
|
||||
|
||||
if (m_Server) {
|
||||
m_Server->Stop();
|
||||
}
|
||||
m_Connexion->CloseConnection();
|
||||
Reset();
|
||||
|
||||
NotifyListeners(&game::ClientListener::OnGameLeave);
|
||||
}
|
||||
|
||||
void Client::Reset() {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "blitz/misc/PrettyLog.h"
|
||||
#include "blitz/protocol/packets/ChatPacket.h"
|
||||
#include "blitz/protocol/packets/ConnexionInfoPacket.h"
|
||||
#include "blitz/protocol/packets/DisconnectPacket.h"
|
||||
#include "blitz/protocol/packets/KeepAlivePacket.h"
|
||||
#include "blitz/protocol/packets/PlayerLoginPacket.h"
|
||||
#include "client/Client.h"
|
||||
@@ -52,7 +53,8 @@ void ClientConnexion::HandlePacket(const protocol::ChatPacket* packet) {
|
||||
}
|
||||
|
||||
void ClientConnexion::HandlePacket(const protocol::DisconnectPacket* packet) {
|
||||
utils::LOG("Disconnected !");
|
||||
utils::LOG("[ClientConnexion] Disconnected ! Reason : " + packet->GetReason());
|
||||
m_Client->NotifyListeners(&game::ClientListener::OnGameLeave);
|
||||
}
|
||||
|
||||
void ClientConnexion::HandlePacket(const protocol::KeepAlivePacket* packet) {
|
||||
@@ -62,6 +64,7 @@ void ClientConnexion::HandlePacket(const protocol::KeepAlivePacket* packet) {
|
||||
|
||||
void ClientConnexion::HandlePacket(const protocol::ConnexionInfoPacket* packet) {
|
||||
m_PlayerID = packet->GetConnectionID();
|
||||
utils::LOGD("[ClientConnexion] Logging in with pseudo " + m_PlayerName + " ...");
|
||||
Login(m_PlayerName);
|
||||
}
|
||||
|
||||
|
||||
27
src/client/audio/AudioBuffer.cpp
Normal file
27
src/client/audio/AudioBuffer.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "client/audio/AudioBuffer.h"
|
||||
|
||||
#include <AL/al.h>
|
||||
|
||||
namespace blitz {
|
||||
namespace audio {
|
||||
|
||||
static const std::vector<unsigned int> AudioFormats = {
|
||||
AL_FORMAT_MONO8,
|
||||
AL_FORMAT_MONO16,
|
||||
AL_FORMAT_STEREO8,
|
||||
AL_FORMAT_STEREO16,
|
||||
};
|
||||
|
||||
AudioBuffer::AudioBuffer(AudioData&& audioData) {
|
||||
alGenBuffers(1, &m_ID);
|
||||
alBufferData(m_ID, AudioFormats[audioData.m_Format], audioData.m_Buffer.data() + audioData.m_Buffer.GetReadOffset(),
|
||||
audioData.m_Buffer.GetSize() - audioData.m_Buffer.GetReadOffset(), static_cast<ALsizei>(audioData.m_Frequency));
|
||||
}
|
||||
|
||||
AudioBuffer::~AudioBuffer() {
|
||||
alDeleteBuffers(1, &m_ID);
|
||||
m_ID = 0;
|
||||
}
|
||||
|
||||
} // namespace audio
|
||||
} // namespace blitz
|
||||
47
src/client/audio/AudioListener.cpp
Normal file
47
src/client/audio/AudioListener.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "client/audio/AudioListener.h"
|
||||
|
||||
#include "blitz/misc/Log.h"
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
|
||||
namespace blitz {
|
||||
namespace audio {
|
||||
|
||||
AudioListener::AudioListener() {
|
||||
ALCdevice* device = alcOpenDevice(nullptr);
|
||||
|
||||
if (device) {
|
||||
ALCcontext* context = alcCreateContext(device, nullptr);
|
||||
alcMakeContextCurrent(context);
|
||||
} else {
|
||||
utils::LOGE("[AudioListener] Can't initialize sound !");
|
||||
}
|
||||
}
|
||||
|
||||
AudioListener::~AudioListener() {
|
||||
ALCcontext* context = alcGetCurrentContext();
|
||||
ALCdevice* device = alcGetContextsDevice(context);
|
||||
alcMakeContextCurrent(NULL);
|
||||
alcDestroyContext(context);
|
||||
alcCloseDevice(device);
|
||||
}
|
||||
|
||||
void AudioListener::SetGain(float a_Gain) {
|
||||
alListenerf(AL_GAIN, a_Gain);
|
||||
}
|
||||
|
||||
void AudioListener::SetPosition(const Vec3f& a_Position) {
|
||||
alListener3f(AL_POSITION, a_Position.x, a_Position.y, a_Position.z);
|
||||
}
|
||||
|
||||
void AudioListener::SetVelocity(const Vec3f& a_Velocity) {
|
||||
alListener3f(AL_VELOCITY, a_Velocity.x, a_Velocity.y, a_Velocity.z);
|
||||
}
|
||||
|
||||
void AudioListener::SetOrientation(const Vec3f& a_Orientation, const Vec3f& a_Up) {
|
||||
Vec3f data[] = {a_Orientation, a_Up};
|
||||
alListenerfv(AL_ORIENTATION, reinterpret_cast<float*>(data));
|
||||
}
|
||||
|
||||
} // namespace audio
|
||||
} // namespace blitz
|
||||
51
src/client/audio/AudioLoader.cpp
Normal file
51
src/client/audio/AudioLoader.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "client/audio/AudioLoader.h"
|
||||
|
||||
#include "blitz/misc/Log.h"
|
||||
#include "client/AssetsManager.h"
|
||||
|
||||
namespace blitz {
|
||||
namespace audio {
|
||||
|
||||
namespace AudioLoader {
|
||||
|
||||
struct WavHeader {
|
||||
/* RIFF Chunk Descriptor */
|
||||
uint8_t RIFF[4]; // RIFF Header Magic header
|
||||
uint32_t ChunkSize; // RIFF Chunk Size
|
||||
uint8_t WAVE[4]; // WAVE Header
|
||||
/* "fmt" sub-chunk */
|
||||
uint8_t FMT[4]; // FMT header
|
||||
uint32_t Subchunk1Size; // Size of the fmt chunk
|
||||
uint16_t AudioFormat; // Audio format 1=PCM,6=mulaw,7=alaw, 257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM
|
||||
uint16_t NumOfChan; // Number of channels 1=Mono 2=Sterio
|
||||
uint32_t SamplesPerSec; // Sampling Frequency in Hz
|
||||
uint32_t BytesPerSec; // bytes per second
|
||||
uint16_t BlockAlign; // 2=16-bit mono, 4=16-bit stereo
|
||||
uint16_t BitsPerSample; // Number of bits per sample
|
||||
/* "data" sub-chunk */
|
||||
uint8_t Subchunk2ID[4]; // "data" string
|
||||
uint32_t Subchunk2Size; // Sampled data length
|
||||
};
|
||||
|
||||
AudioData LoadAudioFile(const std::string& filePath) {
|
||||
|
||||
DataBuffer fileBuffer = utils::AssetsManager::GetAsset(filePath);
|
||||
|
||||
WavHeader wavHeader;
|
||||
fileBuffer >> wavHeader;
|
||||
|
||||
if (wavHeader.Subchunk2Size == 0) {
|
||||
utils::LOGE("[AudioLoader] Failed to load sound " + filePath + " !");
|
||||
}
|
||||
|
||||
fileBuffer.Resize(fileBuffer.GetReadOffset() + wavHeader.Subchunk2Size);
|
||||
|
||||
int audioFormat = (wavHeader.NumOfChan - 1) * 2 + (wavHeader.BitsPerSample / 8) - 1;
|
||||
|
||||
return {fileBuffer, AudioFormat(audioFormat), wavHeader.SamplesPerSec};
|
||||
}
|
||||
|
||||
}; // namespace AudioLoader
|
||||
|
||||
} // namespace audio
|
||||
} // namespace blitz
|
||||
43
src/client/audio/AudioManager.cpp
Normal file
43
src/client/audio/AudioManager.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "client/audio/AudioManager.h"
|
||||
|
||||
#include "client/Client.h"
|
||||
#include "client/audio/AudioBuffer.h"
|
||||
|
||||
namespace blitz {
|
||||
namespace audio {
|
||||
|
||||
AudioManager::AudioManager(Client* client) : m_Client(client) {
|
||||
m_Listener.SetPosition({});
|
||||
m_Listener.SetVelocity({});
|
||||
m_Listener.SetGain(1.0f);
|
||||
m_Listener.SetOrientation(blitz::Vec3f{0.0, 0.0, 0.0}, blitz::Vec3f{0.0, 1.0, 0.0});
|
||||
InitMainMenuMusic();
|
||||
m_MenuMusic->Play();
|
||||
m_Client->BindListener(this);
|
||||
}
|
||||
|
||||
void AudioManager::InitMainMenuMusic() {
|
||||
AudioData audioData = AudioLoader::LoadAudioFile("sessionD.wav");
|
||||
AudioBufferPtr audioBuffer = std::make_unique<AudioBuffer>(std::move(audioData));
|
||||
m_MenuMusic = std::make_unique<AudioSource>();
|
||||
m_MenuMusic->SetBuffer(std::move(audioBuffer));
|
||||
m_MenuMusic->SetPosition({});
|
||||
m_MenuMusic->SetVelocity({});
|
||||
m_MenuMusic->SetPitch(1.0f);
|
||||
m_MenuMusic->SetLooping(true);
|
||||
}
|
||||
|
||||
void AudioManager::OnGameJoin() {
|
||||
m_MenuMusic->Stop();
|
||||
}
|
||||
|
||||
void AudioManager::OnGameLeave() {
|
||||
m_MenuMusic->Play();
|
||||
}
|
||||
|
||||
AudioManager::~AudioManager() {
|
||||
m_Client->UnbindListener(this);
|
||||
}
|
||||
|
||||
} // namespace audio
|
||||
} // namespace blitz
|
||||
66
src/client/audio/AudioSource.cpp
Normal file
66
src/client/audio/AudioSource.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "client/audio/AudioSource.h"
|
||||
|
||||
#include <AL/al.h>
|
||||
|
||||
namespace blitz {
|
||||
namespace audio {
|
||||
|
||||
AudioSource::AudioSource() {
|
||||
alGenSources(1, &m_ID);
|
||||
}
|
||||
|
||||
AudioSource::~AudioSource() {
|
||||
alDeleteSources(1, &m_ID);
|
||||
}
|
||||
|
||||
void AudioSource::SetGain(float a_Gain) {
|
||||
alSourcef(m_ID, AL_GAIN, a_Gain);
|
||||
}
|
||||
|
||||
void AudioSource::SetPitch(float a_Pitch) {
|
||||
alSourcef(m_ID, AL_PITCH, a_Pitch);
|
||||
}
|
||||
|
||||
void AudioSource::SetPosition(const Vec3f& a_Position) {
|
||||
alSource3f(m_ID, AL_POSITION, a_Position.x, a_Position.y, a_Position.z);
|
||||
}
|
||||
|
||||
void AudioSource::SetDirection(const Vec3f& a_Direction) {
|
||||
alSource3f(m_ID, AL_DIRECTION, a_Direction.x, a_Direction.y, a_Direction.z);
|
||||
}
|
||||
|
||||
void AudioSource::SetVelocity(const Vec3f& a_Velocity) {
|
||||
alSource3f(m_ID, AL_VELOCITY, a_Velocity.x, a_Velocity.y, a_Velocity.z);
|
||||
}
|
||||
|
||||
void AudioSource::SetLooping(bool a_Looping) {
|
||||
alSourcei(m_ID, AL_LOOPING, a_Looping);
|
||||
}
|
||||
|
||||
void AudioSource::SetBuffer(AudioBufferPtr&& a_Buffer) {
|
||||
alSourcei(m_ID, AL_BUFFER, a_Buffer->GetID());
|
||||
m_Buffer = std::move(a_Buffer);
|
||||
}
|
||||
|
||||
AudioSource::SourceState AudioSource::GetSourceState() const {
|
||||
int state;
|
||||
|
||||
alGetSourcei(m_ID, AL_SOURCE_STATE, &state);
|
||||
|
||||
return SourceState(state - AL_INITIAL);
|
||||
}
|
||||
|
||||
void AudioSource::Play() {
|
||||
alSourcePlay(m_ID);
|
||||
}
|
||||
|
||||
void AudioSource::Pause() {
|
||||
alSourcePause(m_ID);
|
||||
}
|
||||
|
||||
void AudioSource::Stop() {
|
||||
alSourceStop(m_ID);
|
||||
}
|
||||
|
||||
} // namespace audio
|
||||
} // namespace blitz
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
#include "client/gui/BlitzGui.h"
|
||||
|
||||
#ifndef BLITZ_GL_LOADER_GLBNIDING
|
||||
#ifdef BLITZ_GL_LOADER_GLEW
|
||||
#include <GL/glew.h>
|
||||
#else
|
||||
#include <glbinding/glbinding.h>
|
||||
@@ -97,7 +97,7 @@ bool Display::Create() {
|
||||
SDL_GL_MakeCurrent(m_Window, m_GL_Context);
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
|
||||
#ifndef BLITZ_GL_LOADER_GLBNIDING
|
||||
#ifdef BLITZ_GL_LOADER_GLEW
|
||||
GLenum error = glewInit();
|
||||
if (error != GLEW_OK) {
|
||||
utils::LOGE(utils::Format("[Display] Failed to initialise glew : %s", glewGetErrorString(error)));
|
||||
|
||||
@@ -53,6 +53,7 @@ void ClientGame::HandlePacket(const protocol::PlayerJoinPacket* packet) {
|
||||
// Initialize camera
|
||||
if (packet->GetPlayerID() == m_Client->GetPlayerID()) {
|
||||
m_Client->NotifyListeners(&game::ClientListener::OnSpectatorChange, packet->GetPlayerID());
|
||||
m_Client->NotifyListeners(&game::ClientListener::OnGameJoin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +69,8 @@ void ClientGame::HandlePacket(const protocol::PlayerListPacket* packet) {
|
||||
|
||||
void ClientGame::HandlePacket(const protocol::PlayerStatsPacket* packet) {
|
||||
game::Player* player = m_Client->GetGame()->GetPlayerById(packet->GetPlayerID());
|
||||
assert(player);
|
||||
if (!player)
|
||||
return;
|
||||
player->SetStats(packet->GetPlayerStats());
|
||||
|
||||
m_LeaderBoard.Update();
|
||||
|
||||
@@ -31,6 +31,10 @@ void BlitzGui::SetCustomTheme() {
|
||||
const static ImVec4 colorButtonHover = {0.56f, 0.02f, 0.02f, 1.0f};
|
||||
const static ImVec4 colorButtonActive = {0.36f, 0.03f, 0.03f, 1.0f};
|
||||
const static ImVec4 colorCheckMark = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
const static ImVec4 colorTab = {0.38f, 0.02f, 0.02f, 1.0f};
|
||||
const static ImVec4 colorTabHover = {0.74f, 0.0f, 0.0f, 0.73f};
|
||||
const static ImVec4 colorTabActive = {1.0f, 0.0f, 0.0f, 0.73f};
|
||||
|
||||
|
||||
ImGui::GetStyle().Colors[ImGuiCol_Button] = colorButton;
|
||||
ImGui::GetStyle().Colors[ImGuiCol_ButtonActive] = colorButtonActive;
|
||||
@@ -39,6 +43,15 @@ void BlitzGui::SetCustomTheme() {
|
||||
ImGui::GetStyle().Colors[ImGuiCol_FrameBg] = colorButton;
|
||||
ImGui::GetStyle().Colors[ImGuiCol_FrameBgActive] = colorButtonActive;
|
||||
ImGui::GetStyle().Colors[ImGuiCol_FrameBgHovered] = colorButtonHover;
|
||||
ImGui::GetStyle().Colors[ImGuiCol_Tab] = colorTab;
|
||||
ImGui::GetStyle().Colors[ImGuiCol_TabHovered] = colorTabHover;
|
||||
ImGui::GetStyle().Colors[ImGuiCol_TabActive] = colorTabActive;
|
||||
ImGui::GetStyle().Colors[ImGuiCol_TitleBgActive] = colorTabActive;
|
||||
ImGui::GetStyle().Colors[ImGuiCol_SliderGrab] = colorButton;
|
||||
ImGui::GetStyle().Colors[ImGuiCol_SliderGrabActive] = colorButton;
|
||||
ImGui::GetStyle().Colors[ImGuiCol_HeaderActive] = colorTab;
|
||||
ImGui::GetStyle().Colors[ImGuiCol_HeaderHovered] = colorTabActive;
|
||||
ImGui::GetStyle().Colors[ImGuiCol_Header] = colorTabActive;
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
|
||||
@@ -15,7 +15,7 @@ LeaderBoardGui::LeaderBoardGui(GuiWidget* parent, Client* client) : GuiWidget(pa
|
||||
LeaderBoardGui::~LeaderBoardGui() {}
|
||||
|
||||
void LeaderBoardGui::Draw(const char* title, bool* p_open) {
|
||||
static float leaderboard_width = 640.0f;
|
||||
static float leaderboard_width = 800.0f;
|
||||
static float leaderboard_height = 450.0f;
|
||||
|
||||
ImGuiWindowFlags leaderboard_flags =
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "client/Client.h"
|
||||
#include "client/display/InputManager.h"
|
||||
#include "client/game/ClientGame.h"
|
||||
#include "server/Server.h"
|
||||
#include "server/game/ServerGame.h"
|
||||
#include <imgui.h>
|
||||
@@ -23,8 +24,8 @@ void ServerGui::Render() {
|
||||
ImGuiWindowFlags servergui_flags =
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar;
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
static float servergui_width = 640.0f;
|
||||
static float servergui_height = 640.0f;
|
||||
static float servergui_width = 1500.0f;
|
||||
static float servergui_height = 800.0f;
|
||||
|
||||
const static ImVec2 buttonSize = {300, 60};
|
||||
|
||||
@@ -33,13 +34,47 @@ void ServerGui::Render() {
|
||||
if (ImGui::BeginPopupModal("FENETRE D'ADMIN", nullptr, servergui_flags)) {
|
||||
InputManager::GrabMouse(false);
|
||||
|
||||
ImGui::BeginChild("Mutateurs PARTIE", ImVec2(servergui_width * (5.0f / 10.0f), 0.0f), true);
|
||||
ImGui::Text("PARTIE");
|
||||
ImGui::NewLine();
|
||||
ImGui::BeginChild("Mutateurs JOUEURS", ImVec2(servergui_width * (1.0f / 3.0f), 0.0f), true);
|
||||
{
|
||||
ImGui::Text("JOUEURS");
|
||||
ImGui::NewLine();
|
||||
|
||||
if (ImGui::Button("Ajouter Bot", buttonSize)) {
|
||||
m_Client->GetServer()->AddBot();
|
||||
ImGui::Text("AJOUTER JOUEUR");
|
||||
if (ImGui::Button("Ajouter Bot", buttonSize)) {
|
||||
m_Client->GetServer()->AddBot();
|
||||
}
|
||||
|
||||
ImGui::NewLine();
|
||||
|
||||
ImGui::Text("KICK JOUEURS");
|
||||
bool hasOtherPlayers = false;
|
||||
auto& players = m_Client->GetGame()->GetLeaderBoard().GetPlayers();
|
||||
float kickButtonSizex = ImGui::CalcTextSize("Kick").x + 50.0f;
|
||||
float kickButtonSizey = ImGui::CalcTextSize("Kick").y + 20.0f;
|
||||
for (game::Player* player : players) {
|
||||
if (player->GetID() == m_Client->GetPlayerID()) {
|
||||
continue; // Skip the current player
|
||||
}
|
||||
hasOtherPlayers = true;
|
||||
ImGui::Text("%s", player->GetName().c_str());
|
||||
ImGui::SameLine();
|
||||
ImGui::PushID(player->GetID());
|
||||
if (ImGui::Button("Kick", {kickButtonSizex, kickButtonSizey})) {
|
||||
m_Client->GetServer()->KickPlayer(player->GetID());
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
if (!hasOtherPlayers) {
|
||||
ImGui::Text("Aucun autre joueur");
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginChild("Mutateurs PARTIE", ImVec2(servergui_width * (1.0f / 3.0f), 0.0f), true);
|
||||
ImGui::Text("PARTIE");
|
||||
|
||||
ImGui::NewLine();
|
||||
ImGui::Text("GRAVITE");
|
||||
@@ -86,7 +121,7 @@ void ServerGui::Render() {
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginChild("MUTATEUR TEMPS", ImVec2(servergui_width * (4.5f / 10.0f), 0.0f), true);
|
||||
ImGui::BeginChild("MUTATEUR TEMPS", ImVec2(servergui_width * (0.9f / 3.0f), 0.0f), true);
|
||||
static int gameDurMin = static_cast<int>(m_Client->GetServer()->GetGame().GetServerDuration().m_GameDuration / 1000) / 60;
|
||||
static int gameDurSec = static_cast<int>(m_Client->GetServer()->GetGame().GetServerDuration().m_GameDuration / 1000) % 60;
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ void Server::Accept() {
|
||||
if (m_Listener.Accept(newSocket)) {
|
||||
game::PlayerID newPlayerID = GetNewPlayerID();
|
||||
auto con = std::make_unique<ServerConnexion>(this, newSocket, newPlayerID);
|
||||
m_Connections.insert(std::move(ConnexionMap::value_type{newPlayerID, std::move(con)}));
|
||||
m_Connections.insert(ConnexionMap::value_type{newPlayerID, std::move(con)});
|
||||
m_Connections[newPlayerID]->Start();
|
||||
newPlayerID++;
|
||||
}
|
||||
@@ -153,9 +153,6 @@ void Server::BroadcastChatMessage(const std::string& msg) {
|
||||
|
||||
void Server::RemoveConnexion(std::uint8_t connexionID) {
|
||||
m_Connections.erase(connexionID);
|
||||
|
||||
protocol::PlayerLeavePacket packet(connexionID);
|
||||
BroadcastPacket(&packet);
|
||||
}
|
||||
|
||||
std::uint16_t Server::GetListeningPort() {
|
||||
@@ -173,5 +170,26 @@ void Server::AddBot() {
|
||||
botPlayer->SetBot();
|
||||
}
|
||||
|
||||
void Server::KickPlayer(game::PlayerID playerID) {
|
||||
auto it = m_Connections.find(playerID);
|
||||
|
||||
if (it != m_Connections.end()) {
|
||||
protocol::DisconnectPacket packet("Tu dois disparaître (in game) !");
|
||||
m_Connections.at(playerID)->SendPacket(&packet);
|
||||
}
|
||||
|
||||
game::Player* player = m_Game.GetPlayerById(playerID);
|
||||
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
m_Game.RemovePlayer(playerID);
|
||||
|
||||
if (player->IsBot()) {
|
||||
} else {
|
||||
RemoveConnexion(playerID);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace server
|
||||
} // namespace blitz
|
||||
|
||||
@@ -179,12 +179,7 @@ ServerConnexion::~ServerConnexion() {
|
||||
|
||||
GetDispatcher()->UnregisterHandler(this);
|
||||
|
||||
if (m_Player) {
|
||||
std::string leaveMessage = utils::Format("%s a quitte la partie !", m_Player->GetName().c_str());
|
||||
|
||||
utils::LOG("[Server] " + leaveMessage);
|
||||
m_Server->BroadcastChatMessage(protocol::ChatPacket::GetTextColor(protocol::YELLOW) + leaveMessage);
|
||||
}
|
||||
m_Server->GetGame().RemovePlayer(m_ID);
|
||||
}
|
||||
|
||||
} // namespace server
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "blitz/misc/Random.h"
|
||||
#include "blitz/protocol/packets/ChatPacket.h"
|
||||
#include "blitz/protocol/packets/PlayerJoinPacket.h"
|
||||
#include "blitz/protocol/packets/PlayerLeavePacket.h"
|
||||
#include "blitz/protocol/packets/PlayerPositionAndRotationPacket.h"
|
||||
#include "blitz/protocol/packets/PlayerStatsPacket.h"
|
||||
#include "blitz/protocol/packets/ServerConfigPacket.h"
|
||||
@@ -36,6 +37,7 @@ void ServerGame::Tick(std::uint64_t delta) {
|
||||
if (m_PositionTimer.Update(delta)) {
|
||||
SendPlayerPositions();
|
||||
}
|
||||
|
||||
if (m_GameState != game::gsWaiting && m_GameTimer.Update(delta)) {
|
||||
switch (m_GameState) {
|
||||
case game::gsPreparing:
|
||||
@@ -112,10 +114,24 @@ void ServerGame::AddPlayer(game::PlayerID player, const std::string& name) {
|
||||
}
|
||||
}
|
||||
|
||||
void ServerGame::RemovePlayer(game::PlayerID player) {
|
||||
Game::RemovePlayer(player);
|
||||
void ServerGame::RemovePlayer(game::PlayerID playerID) {
|
||||
|
||||
if (m_GameState == game::gsGame && m_Players.size() <= 1) {
|
||||
game::Player* player = GetPlayerById(playerID);
|
||||
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
protocol::PlayerLeavePacket packet(playerID);
|
||||
m_Server->BroadcastPacket(&packet);
|
||||
|
||||
std::string leaveMessage = utils::Format("%s a quitte la partie !", player->GetName().c_str());
|
||||
|
||||
utils::LOG("[Server] " + leaveMessage);
|
||||
m_Server->BroadcastChatMessage(protocol::ChatPacket::GetTextColor(protocol::YELLOW) + leaveMessage);
|
||||
|
||||
Game::RemovePlayer(playerID);
|
||||
|
||||
if ((m_GameState == game::gsGame || m_GameState == game::gsPreparing) && m_Players.size() <= 1) {
|
||||
CancelGame();
|
||||
}
|
||||
}
|
||||
@@ -144,7 +160,10 @@ void ServerGame::UpdateHP(game::Player& player, float newHP) {
|
||||
return;
|
||||
|
||||
protocol::UpdateHealthPacket packet(player.GetHP());
|
||||
m_Server->GetConnexions().at(player.GetID())->SendPacket(&packet);
|
||||
|
||||
auto it = m_Server->GetConnexions().find(player.GetID());
|
||||
if (it != m_Server->GetConnexions().end())
|
||||
it->second->SendPacket(&packet);
|
||||
}
|
||||
|
||||
void ServerGame::UpdatePlayerStats() {
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
includes("Blitz.lua")
|
||||
|
||||
-- This should be temporary
|
||||
opengl = "glbinding"
|
||||
if is_plat("linux") and is_arch("arm64-v8a") then
|
||||
opengl = "glew"
|
||||
add_defines("BLITZ_GL_LOADER_GLEW")
|
||||
end
|
||||
|
||||
add_requires("libsdl 2.28.3", {configs = {sdlmain = false}})
|
||||
add_requires("glew", "assimp", "nlohmann_json")
|
||||
add_requires(opengl, "assimp 5.3.1", "nlohmann_json", "openal-soft")
|
||||
|
||||
|
||||
-- Client binary (default)
|
||||
target("BlitzClient")
|
||||
@@ -18,7 +26,7 @@ target("BlitzClient")
|
||||
|
||||
-- Libraries
|
||||
add_deps("Blitz")
|
||||
add_packages("libsdl", "glew", "assimp", "nlohmann_json")
|
||||
add_packages("libsdl", opengl, "assimp", "nlohmann_json", "openal-soft")
|
||||
|
||||
add_includedirs("../libs", "../libs/imgui")
|
||||
add_files("../libs/imgui/**.cpp")
|
||||
|
||||
Reference in New Issue
Block a user