TRAZE: removed traze client
looks like traze is no longer available at https://traze.iteratec.de/master
parent
fdee3de225
commit
f5d7b190d7
|
@ -9,7 +9,7 @@ jobs:
|
|||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Setup
|
||||
run: brew install mosquitto libuv sdl2 libpq sdl2_mixer python
|
||||
run: brew install libuv sdl2 libpq sdl2_mixer python
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
|
@ -36,7 +36,6 @@ jobs:
|
|||
ctest -V -R tests-poi$
|
||||
ctest -V -R tests-shadertool$
|
||||
ctest -V -R tests-stock$
|
||||
ctest -V -R tests-testtraze$
|
||||
ctest -V -R tests-util$
|
||||
ctest -V -R tests-uuidutil$
|
||||
ctest -V -R tests-video$
|
||||
|
@ -130,7 +129,6 @@ jobs:
|
|||
ctest -V -R tests-poi$
|
||||
ctest -V -R tests-shadertool$
|
||||
ctest -V -R tests-stock$
|
||||
ctest -V -R tests-testtraze$
|
||||
ctest -V -R tests-util$
|
||||
ctest -V -R tests-uuidutil$
|
||||
ctest -V -R tests-video$
|
||||
|
|
|
@ -827,8 +827,7 @@ RECURSIVE = YES
|
|||
# run.
|
||||
|
||||
EXCLUDE = @CMAKE_CURRENT_SOURCE_DIR@/src/tools/glslang \
|
||||
@CMAKE_CURRENT_SOURCE_DIR@/contrib \
|
||||
@CMAKE_CURRENT_SOURCE_DIR@/src/tests/testtraze/json.hpp
|
||||
@CMAKE_CURRENT_SOURCE_DIR@/contrib
|
||||
|
||||
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
|
||||
# directories that are symbolic links (a Unix file system feature) are excluded
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
include("${ROOT_DIR}/cmake/macros.cmake")
|
||||
engine_find(mosquitto mosquitto.h "" "" "")
|
|
@ -17,7 +17,6 @@
|
|||
* gtest
|
||||
* opencl (optional)
|
||||
* libuuid
|
||||
* mosquitto (optional)
|
||||
|
||||
Some of these dependencies might not be available as packages in your toolchain - most
|
||||
of them are also bundled with the application. But local installed headers always have
|
||||
|
@ -50,11 +49,11 @@ port install postgresql95-server
|
|||
## Brew
|
||||
|
||||
```bash
|
||||
brew install mosquitto libuv sdl2 libpq sdl2_mixer
|
||||
brew install libuv sdl2 libpq sdl2_mixer
|
||||
```
|
||||
|
||||
## Windows
|
||||
|
||||
```bash
|
||||
vcpkg install sdl2 libuv libpq lua glm glslang gtest mosquitto
|
||||
vcpkg install sdl2 libuv libpq lua glm glslang gtest
|
||||
```
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
# Traze
|
||||
|
||||
This is a voxel based representation of the traze game available at [traze.iteratec.de](https://traze.iteratec.de).
|
||||
|
||||
It uses mqtt for communication. The lib that is used for this is [mosquitto](https://github.com/eclipse/mosquitto).
|
||||
|
||||
This application can be used to spectate games and to play games. You can select all active games from the debug menu,
|
||||
select one and decide at any time to join the game by hitting the button in the debug menu. Once you press the first WASD-key
|
||||
to move your bike, you are joined.
|
||||
|
||||
![image](https://raw.githubusercontent.com/wiki/mgerhardy/engine/images/traze.png)
|
||||
|
||||
# TODO
|
||||
|
||||
* add scoreboard menu
|
||||
* player explosions
|
||||
* fix voxelfont performance issues
|
|
@ -92,10 +92,6 @@ Visit the frustum in the octree.
|
|||
|
||||
Just an empty template for new test applications.
|
||||
|
||||
## testtraze
|
||||
|
||||
* [traze client](Traze.md)
|
||||
|
||||
## testhttpserver
|
||||
|
||||
A test application around the http module server for e.g. fuzzy testing purposes.
|
||||
|
|
|
@ -46,5 +46,4 @@ nav:
|
|||
- AI development: AIRemoteDebugger.md
|
||||
- Test applications:
|
||||
- TestAnimation: TestAnimation.md
|
||||
- Traze: Traze.md
|
||||
- VisualTests.md
|
||||
|
|
|
@ -16,7 +16,6 @@ add_subdirectory(testshapebuilder)
|
|||
add_subdirectory(testvoxelfont)
|
||||
add_subdirectory(testvoxelgpu)
|
||||
add_subdirectory(testcomputetexture3d)
|
||||
add_subdirectory(testtraze)
|
||||
add_subdirectory(testhttpserver)
|
||||
add_subdirectory(testskybox)
|
||||
add_subdirectory(testbiomes)
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
project(testtraze)
|
||||
find_package(Mosquitto)
|
||||
|
||||
if (MOSQUITTO_FOUND)
|
||||
set(SRCS
|
||||
JSON.h json.hpp
|
||||
TrazeEvents.h
|
||||
TrazeProtocol.h TrazeProtocol.cpp
|
||||
TrazeTypes.h
|
||||
TestTraze.h TestTraze.cpp
|
||||
)
|
||||
set(FILES
|
||||
shared/font.ttf
|
||||
testtraze/testtraze-keybindings.cfg
|
||||
testtraze/sound/frag.ogg
|
||||
testtraze/sound/suicide.ogg
|
||||
testtraze/sound/collision.ogg
|
||||
testtraze/sound/join.ogg
|
||||
testtraze/sound/you_lose.ogg
|
||||
testtraze/sound/you_win.ogg
|
||||
)
|
||||
engine_add_executable(TARGET ${PROJECT_NAME} SRCS ${SRCS} FILES ${FILES} WINDOWED NOINSTALL)
|
||||
engine_target_link_libraries(TARGET ${PROJECT_NAME} DEPENDENCIES testcore uuidutil voxelrender voxelfont util audio ${MOSQUITTO_LIBRARIES})
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${MOSQUITTO_INCLUDE_DIRS})
|
||||
|
||||
gtest_suite_begin(tests-${PROJECT_NAME} TEMPLATE ${ROOT_DIR}/src/modules/core/tests/main.cpp.in)
|
||||
gtest_suite_sources(tests-${PROJECT_NAME}
|
||||
tests/TrazeProtocolTest.cpp
|
||||
TrazeProtocol.cpp
|
||||
)
|
||||
gtest_suite_deps(tests-${PROJECT_NAME} test-app uuidutil voxel ${MOSQUITTO_LIBRARIES})
|
||||
if (UNITTESTS)
|
||||
target_include_directories(tests-${PROJECT_NAME} PRIVATE ${MOSQUITTO_INCLUDE_DIRS})
|
||||
endif()
|
||||
gtest_suite_end(tests-${PROJECT_NAME})
|
||||
else()
|
||||
message(WARNING "${PROJECT_NAME} is disabled - libmosquitto wasn't found")
|
||||
endif()
|
|
@ -1,74 +0,0 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define JSON_NOEXCEPTION
|
||||
#include "json.hpp"
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
namespace core {
|
||||
|
||||
using json = ::nlohmann::json;
|
||||
|
||||
}
|
||||
|
||||
namespace glm {
|
||||
template<typename T, qualifier Q = defaultp>
|
||||
void to_json(::core::json& j, const vec<2, T, Q>& p) {
|
||||
j = ::core::json{{"x", p.x}, {"y", p.y}};
|
||||
}
|
||||
|
||||
template<typename T, qualifier Q = defaultp>
|
||||
void from_json(const ::core::json& j, vec<2, T, Q>& p) {
|
||||
if (j.is_array() && j.size() == 2) {
|
||||
p.x = j[0].get<T>();
|
||||
p.y = j[1].get<T>();
|
||||
return;
|
||||
}
|
||||
p.x = j.at("x").get<T>();
|
||||
p.y = j.at("y").get<T>();
|
||||
}
|
||||
|
||||
template<typename T, qualifier Q = defaultp>
|
||||
void to_json(::core::json& j, const vec<3, T, Q>& p) {
|
||||
j = ::core::json{{"x", p.x}, {"y", p.y}, {"z", p.z}};
|
||||
}
|
||||
|
||||
template<typename T, qualifier Q = defaultp>
|
||||
void from_json(const ::core::json& j, vec<3, T, Q>& p) {
|
||||
if (j.is_array() && j.size() == 3) {
|
||||
p.x = j[0].get<T>();
|
||||
p.y = j[1].get<T>();
|
||||
p.z = j[2].get<T>();
|
||||
return;
|
||||
}
|
||||
p.x = j.at("x").get<T>();
|
||||
p.y = j.at("y").get<T>();
|
||||
p.z = j.at("z").get<T>();
|
||||
}
|
||||
|
||||
template<typename T, qualifier Q = defaultp>
|
||||
void to_json(::core::json& j, const vec<4, T, Q>& p) {
|
||||
j = ::core::json{{"x", p.x}, {"y", p.y}, {"z", p.z}, {"w", p.w}};
|
||||
}
|
||||
|
||||
template<typename T, qualifier Q = defaultp>
|
||||
void from_json(const ::core::json& j, vec<4, T, Q>& p) {
|
||||
if (j.is_array() && j.size() == 4) {
|
||||
p.x = j[0].get<T>();
|
||||
p.y = j[1].get<T>();
|
||||
p.z = j[2].get<T>();
|
||||
p.w = j[3].get<T>();
|
||||
return;
|
||||
}
|
||||
p.x = j.at("x").get<T>();
|
||||
p.y = j.at("y").get<T>();
|
||||
p.z = j.at("z").get<T>();
|
||||
p.z = j.at("w").get<T>();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,374 +0,0 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
#include "TestTraze.h"
|
||||
#include "core/SharedPtr.h"
|
||||
#include "command/Command.h"
|
||||
#include "testcore/TestAppMain.h"
|
||||
#include "voxel/MaterialColor.h"
|
||||
#include "voxel/Region.h"
|
||||
#include "voxel/RawVolumeWrapper.h"
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
namespace {
|
||||
const int PlayFieldVolume = 0;
|
||||
const int FloorVolume = 1;
|
||||
const int FontSize = 48;
|
||||
}
|
||||
|
||||
TestTraze::TestTraze(const metric::MetricPtr& metric, const io::FilesystemPtr& filesystem, const core::EventBusPtr& eventBus, const core::TimeProviderPtr& timeProvider) :
|
||||
Super(metric, filesystem, eventBus, timeProvider), _protocol(eventBus), _voxelFontRender(FontSize, 4, voxel::VoxelFont::OriginUpperLeft), _soundMgr(filesystem) {
|
||||
init(ORGANISATION, "testtraze");
|
||||
setRenderAxis(false);
|
||||
setRelativeMouseMode(false);
|
||||
_allowRelativeMouseMode = false;
|
||||
_eventBus->subscribe<traze::NewGridEvent>(*this);
|
||||
_eventBus->subscribe<traze::NewGamesEvent>(*this);
|
||||
_eventBus->subscribe<traze::PlayerListEvent>(*this);
|
||||
_eventBus->subscribe<traze::TickerEvent>(*this);
|
||||
_eventBus->subscribe<traze::SpawnEvent>(*this);
|
||||
_eventBus->subscribe<traze::BikeEvent>(*this);
|
||||
_eventBus->subscribe<traze::ScoreEvent>(*this);
|
||||
}
|
||||
|
||||
const core::String& TestTraze::playerName(traze::PlayerId playerId) const {
|
||||
return player(playerId).name;
|
||||
}
|
||||
|
||||
const traze::Player& TestTraze::player(traze::PlayerId playerId) const {
|
||||
for (auto& p : _players) {
|
||||
if (p.id == playerId) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
static traze::Player player;
|
||||
return player;
|
||||
}
|
||||
|
||||
app::AppState TestTraze::onConstruct() {
|
||||
app::AppState state = Super::onConstruct();
|
||||
_framesPerSecondsCap->setVal(60.0f);
|
||||
core::Var::get("mosquitto_host", "traze.iteratec.de");
|
||||
core::Var::get("mosquitto_port", "1883");
|
||||
_name = core::Var::get("name", "noname_testtraze");
|
||||
command::Command::registerCommand("join", [&] (const command::CmdArgs& args) { _protocol.join(_name->strVal()); });
|
||||
command::Command::registerCommand("bail", [&] (const command::CmdArgs& args) { _protocol.bail(); });
|
||||
command::Command::registerCommand("left", [&] (const command::CmdArgs& args) { _protocol.steer(traze::BikeDirection::W); });
|
||||
command::Command::registerCommand("right", [&] (const command::CmdArgs& args) { _protocol.steer(traze::BikeDirection::E); });
|
||||
command::Command::registerCommand("forward", [&] (const command::CmdArgs& args) { _protocol.steer(traze::BikeDirection::N); });
|
||||
command::Command::registerCommand("backward", [&] (const command::CmdArgs& args) { _protocol.steer(traze::BikeDirection::S); });
|
||||
command::Command::registerCommand("players", [&] (const command::CmdArgs& args) {
|
||||
for (const auto& p : _players) {
|
||||
Log::info("%s", p.name.c_str());
|
||||
}
|
||||
});
|
||||
core::Var::get(cfg::VoxelMeshSize, "16", core::CV_READONLY);
|
||||
_rawVolumeRenderer.construct();
|
||||
_messageQueue.construct();
|
||||
_soundMgr.construct();
|
||||
return state;
|
||||
}
|
||||
|
||||
void TestTraze::sound(const char *soundId) {
|
||||
if (_soundMgr.play(-1, soundId, glm::ivec3(0), false) < 0) {
|
||||
Log::warn("Failed to play sound %s", soundId);
|
||||
}
|
||||
}
|
||||
|
||||
app::AppState TestTraze::onInit() {
|
||||
app::AppState state = Super::onInit();
|
||||
if (state != app::AppState::Running) {
|
||||
return state;
|
||||
}
|
||||
if (!voxel::initDefaultMaterialColors()) {
|
||||
Log::error("Failed to initialize the palette data");
|
||||
return app::AppState::InitFailure;
|
||||
}
|
||||
if (!_protocol.init()) {
|
||||
Log::error("Failed to init protocol");
|
||||
return app::AppState::InitFailure;
|
||||
}
|
||||
if (!_rawVolumeRenderer.init()) {
|
||||
Log::error("Failed to initialize the raw volume renderer");
|
||||
return app::AppState::InitFailure;
|
||||
}
|
||||
if (!_messageQueue.init()) {
|
||||
Log::error("Failed to init message queue");
|
||||
return app::AppState::InitFailure;
|
||||
}
|
||||
if (!_voxelFontRender.init()) {
|
||||
Log::error("Failed to init voxel font");
|
||||
return app::AppState::InitFailure;
|
||||
}
|
||||
if (!_soundMgr.init()) {
|
||||
Log::error("Failed to init sound manager");
|
||||
return app::AppState::InitFailure;
|
||||
}
|
||||
|
||||
camera().setWorldPosition(glm::vec3(0.0f, 50.0f, 84.0f));
|
||||
_logLevelVar->setVal(core::string::toString(SDL_LOG_PRIORITY_INFO));
|
||||
Log::init();
|
||||
|
||||
_textCamera.setMode(video::CameraMode::Orthogonal);
|
||||
_textCamera.setNearPlane(-10.0f);
|
||||
_textCamera.setFarPlane(10.0f);
|
||||
_textCamera.setSize(windowDimension());
|
||||
_textCamera.update(0.0);
|
||||
|
||||
_voxelFontRender.setViewProjectionMatrix(_textCamera.viewProjectionMatrix());
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void TestTraze::onEvent(const traze::NewGamesEvent& event) {
|
||||
_games = event.get();
|
||||
Log::debug("Got %i games", (int)_games.size());
|
||||
// there are some points were we assume a limited amount of games...
|
||||
if (_games.size() >= UCHAR_MAX) {
|
||||
Log::warn("Too many games found - reducing them");
|
||||
_games.resize(UCHAR_MAX - 1);
|
||||
}
|
||||
// TODO: this doesn't work if the instanceName changed (new game added, old game removed...)
|
||||
if (_games.empty() || _currentGameIndex > (int8_t)_games.size()) {
|
||||
_protocol.unsubscribe();
|
||||
_currentGameIndex = -1;
|
||||
} else if (!_games.empty() && _currentGameIndex == -1) {
|
||||
Log::info("Select first game");
|
||||
_currentGameIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void TestTraze::onEvent(const traze::BikeEvent& event) {
|
||||
const traze::Bike& bike = event.get();
|
||||
Log::debug("Received bike event for player %u (%i:%i)",
|
||||
bike.playerId, bike.currentLocation.x, bike.currentLocation.y);
|
||||
}
|
||||
|
||||
void TestTraze::onEvent(const traze::TickerEvent& event) {
|
||||
const traze::Ticker& ticker = event.get();
|
||||
const core::String& fraggerName = playerName(ticker.fragger);
|
||||
const core::String& casualtyName = playerName(ticker.casualty);
|
||||
switch (ticker.type) {
|
||||
case traze::TickerType::Frag:
|
||||
if (ticker.fragger == _protocol.playerId()) {
|
||||
sound("you_win");
|
||||
_messageQueue.message("You fragged %s", casualtyName.c_str());
|
||||
} else if (ticker.casualty == (int)_protocol.playerId()) {
|
||||
sound("you_lose");
|
||||
_messageQueue.message("You were fragged by %s", fraggerName.c_str());
|
||||
} else {
|
||||
_messageQueue.message("%s fragged %s", fraggerName.c_str(), casualtyName.c_str());
|
||||
}
|
||||
break;
|
||||
case traze::TickerType::Suicide:
|
||||
if (ticker.casualty == (int)_protocol.playerId()) {
|
||||
sound("you_lose");
|
||||
} else {
|
||||
sound("suicide");
|
||||
}
|
||||
_messageQueue.message("%s committed suicide", fraggerName.c_str());
|
||||
break;
|
||||
case traze::TickerType::Collision:
|
||||
if (ticker.casualty == (int)_protocol.playerId()) {
|
||||
sound("you_lose");
|
||||
} else if (ticker.fragger == _protocol.playerId()) {
|
||||
sound("you_win");
|
||||
} else {
|
||||
sound("collision");
|
||||
}
|
||||
_messageQueue.message("%s - collision with another player", fraggerName.c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TestTraze::onEvent(const traze::ScoreEvent& event) {
|
||||
const traze::Score& score = event.get();
|
||||
Log::debug("Received score event with %i entries", (int)score.size());
|
||||
}
|
||||
|
||||
void TestTraze::onEvent(const traze::SpawnEvent& event) {
|
||||
const traze::Spawn& spawn = event.get();
|
||||
Log::debug("Spawn at position %i:%i", spawn.position.x, spawn.position.y);
|
||||
if (spawn.own) {
|
||||
_spawnPosition = spawn.position;
|
||||
_spawnTime = _nowSeconds;
|
||||
sound("join");
|
||||
}
|
||||
}
|
||||
|
||||
void TestTraze::onEvent(const traze::NewGridEvent& event) {
|
||||
core::SharedPtr<voxel::RawVolume> v = event.get();
|
||||
if (_spawnTime > 0.0 && _nowSeconds - _spawnTime < 4.0) {
|
||||
const voxel::Voxel voxel = voxel::createRandomColorVoxel(voxel::VoxelType::Generic);
|
||||
v->setVoxel(glm::ivec3(_spawnPosition.y, 0, _spawnPosition.x), voxel);
|
||||
v->setVoxel(glm::ivec3(_spawnPosition.y, 1, _spawnPosition.x), voxel);
|
||||
}
|
||||
voxel::RawVolume* volume = _rawVolumeRenderer.volume(PlayFieldVolume);
|
||||
voxel::Region dirtyRegion;
|
||||
if (volume == nullptr || volume->region() != v->region()) {
|
||||
volume = new voxel::RawVolume(v.get());
|
||||
delete _rawVolumeRenderer.setVolume(PlayFieldVolume, volume);
|
||||
dirtyRegion = v->region();
|
||||
|
||||
voxel::RawVolume* floor = new voxel::RawVolume(dirtyRegion);
|
||||
const voxel::Region& region = floor->region();
|
||||
const voxel::Voxel voxel = voxel::createColorVoxel(voxel::VoxelType::Dirt, 0);
|
||||
// ground
|
||||
for (int32_t z = region.getLowerZ(); z <= region.getUpperZ(); ++z) {
|
||||
for (int32_t x = region.getLowerX(); x <= region.getUpperX(); ++x) {
|
||||
floor->setVoxel(x, region.getLowerY(), z, voxel);
|
||||
}
|
||||
}
|
||||
// walls
|
||||
for (int32_t z = region.getLowerZ(); z <= region.getUpperZ(); ++z) {
|
||||
floor->setVoxel(region.getLowerX(), region.getLowerY() + 1, z, voxel);
|
||||
floor->setVoxel(region.getUpperX(), region.getLowerY() + 1, z, voxel);
|
||||
floor->setVoxel(region.getLowerX(), region.getLowerY() + 2, z, voxel);
|
||||
floor->setVoxel(region.getUpperX(), region.getLowerY() + 2, z, voxel);
|
||||
}
|
||||
for (int32_t x = region.getLowerX(); x <= region.getUpperX(); ++x) {
|
||||
floor->setVoxel(x, region.getLowerY() + 1, region.getLowerZ(), voxel);
|
||||
floor->setVoxel(x, region.getLowerY() + 1, region.getUpperZ(), voxel);
|
||||
floor->setVoxel(x, region.getLowerY() + 2, region.getLowerZ(), voxel);
|
||||
floor->setVoxel(x, region.getLowerY() + 2, region.getUpperZ(), voxel);
|
||||
}
|
||||
delete _rawVolumeRenderer.setVolume(FloorVolume, floor);
|
||||
if (!_rawVolumeRenderer.extractRegion(FloorVolume, region)) {
|
||||
Log::error("Failed to extract the volume");
|
||||
}
|
||||
} else {
|
||||
voxel::RawVolumeWrapper wrapper(volume);
|
||||
const voxel::Region& region = volume->region();
|
||||
const glm::ivec3& mins = region.getLowerCorner();
|
||||
const glm::ivec3& maxs = region.getUpperCorner();
|
||||
glm::ivec3 p;
|
||||
for (int x = mins.x; x <= maxs.x; ++x) {
|
||||
p.x = x;
|
||||
for (int y = mins.y; y <= maxs.y; ++y) {
|
||||
p.y = y;
|
||||
for (int z = mins.z; z <= maxs.z; ++z) {
|
||||
p.z = z;
|
||||
wrapper.setVoxel(p, v->voxel(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
dirtyRegion = wrapper.dirtyRegion();
|
||||
}
|
||||
const glm::mat4& translate = glm::translate(-volume->region().getCenter());
|
||||
const glm::mat4& rotateY = glm::rotate(glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
const glm::mat4& rotateX = glm::rotate(glm::radians(25.0f), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
const glm::mat4 model = rotateX * rotateY * translate;
|
||||
_rawVolumeRenderer.setModelMatrix(PlayFieldVolume, model);
|
||||
_rawVolumeRenderer.setModelMatrix(FloorVolume, model);
|
||||
if (!_rawVolumeRenderer.extractRegion(PlayFieldVolume, dirtyRegion)) {
|
||||
Log::error("Failed to extract the volume");
|
||||
}
|
||||
}
|
||||
|
||||
void TestTraze::onEvent(const traze::PlayerListEvent& event) {
|
||||
_players = event.get();
|
||||
_maxLength = 200;
|
||||
for (const traze::Player& p : _players) {
|
||||
_maxLength = core_max(_maxLength, _voxelFontRender.stringWidth(p.name.c_str(), p.name.size()) + 60);
|
||||
}
|
||||
}
|
||||
|
||||
app::AppState TestTraze::onRunning() {
|
||||
_rawVolumeRenderer.update();
|
||||
app::AppState state = Super::onRunning();
|
||||
if (!_protocol.connected()) {
|
||||
if (_nextConnectTime <= 0.0) {
|
||||
_nextConnectTime = 3.0;
|
||||
_protocol.connect();
|
||||
} else {
|
||||
_nextConnectTime -= deltaFrameSeconds();
|
||||
}
|
||||
} else if (_currentGameIndex != -1) {
|
||||
_protocol.subscribe(_games[_currentGameIndex]);
|
||||
}
|
||||
_messageQueue.update(_deltaFrameSeconds);
|
||||
_soundMgr.setListenerPosition(camera().worldPosition());
|
||||
_soundMgr.update();
|
||||
return state;
|
||||
}
|
||||
|
||||
app::AppState TestTraze::onCleanup() {
|
||||
_voxelFontRender.shutdown();
|
||||
_soundMgr.shutdown();
|
||||
const core::DynamicArray<voxel::RawVolume*>& old = _rawVolumeRenderer.shutdown();
|
||||
for (voxel::RawVolume* v : old) {
|
||||
delete v;
|
||||
}
|
||||
_protocol.shutdown();
|
||||
_messageQueue.shutdown();
|
||||
return Super::onCleanup();
|
||||
}
|
||||
|
||||
void TestTraze::onRenderUI() {
|
||||
if (ImGui::BeginCombo("GameInfo", _currentGameIndex == -1 ? "" : _games[_currentGameIndex].name.c_str(), 0)) {
|
||||
for (size_t i = 0u; i < (size_t)_games.size(); ++i) {
|
||||
const traze::GameInfo& game = _games[i];
|
||||
const bool selected = _currentGameIndex == (int)i;
|
||||
if (ImGui::Selectable(game.name.c_str(), selected)) {
|
||||
_currentGameIndex = i;
|
||||
}
|
||||
if (selected) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::InputVarString("Name", _name);
|
||||
if (!_protocol.joined() && ImGui::Button("Join")) {
|
||||
_protocol.join(_name->strVal());
|
||||
}
|
||||
if (_protocol.joined() && ImGui::Button("Leave")) {
|
||||
_protocol.bail();
|
||||
}
|
||||
ImGui::Checkbox("Render board", &_renderBoard);
|
||||
ImGui::Checkbox("Render player names", &_renderPlayerNames);
|
||||
Super::onRenderUI();
|
||||
}
|
||||
|
||||
void TestTraze::doRender() {
|
||||
if (_renderBoard) {
|
||||
_rawVolumeRenderer.render(camera());
|
||||
}
|
||||
|
||||
const glm::ivec2& dim = frameBufferDimension();
|
||||
_voxelFontRender.setModelMatrix(glm::translate(glm::vec3(dim.x / 3, 0.0f, 0.0f)));
|
||||
int messageOffset = 0;
|
||||
_messageQueue.visitMessages([&] (int64_t /*remainingMillis*/, const core::String& msg) {
|
||||
_voxelFontRender.text(glm::ivec3(0.0f, (float)messageOffset, 0.0f), core::Color::White, "%s", msg.c_str());
|
||||
messageOffset += _voxelFontRender.lineHeight();
|
||||
});
|
||||
_voxelFontRender.swapBuffers();
|
||||
_voxelFontRender.render();
|
||||
|
||||
if (!_protocol.connected()) {
|
||||
const char* connecting = "Connecting";
|
||||
const int w = _voxelFontRender.stringWidth(connecting, SDL_strlen(connecting));
|
||||
_voxelFontRender.setModelMatrix(glm::translate(glm::vec3(dim.x / 2 - w / 2, dim.y / 2 - _voxelFontRender.lineHeight() / 2, 0.0f)));
|
||||
const glm::ivec3 pos(0, 0, 0);
|
||||
_voxelFontRender.text(pos, core::Color::Red, "%s", connecting);
|
||||
_voxelFontRender.text(glm::ivec3(pos.x, pos.y + _voxelFontRender.lineHeight(), pos.z), core::Color::Red, ".");
|
||||
} else if (_renderPlayerNames) {
|
||||
_voxelFontRender.setModelMatrix(glm::translate(glm::vec3(20.0f, 20.0f, 0.0f)));
|
||||
int yOffset = 0;
|
||||
_voxelFontRender.text(glm::ivec3(0, yOffset, 0), core::Color::White, "%i Players", (int)_players.size());
|
||||
yOffset += _voxelFontRender.lineHeight();
|
||||
for (const traze::Player& p : _players) {
|
||||
_voxelFontRender.text(glm::ivec3(0, yOffset, 0), p.color, "* %s", p.name.c_str());
|
||||
_voxelFontRender.text(glm::ivec3(_maxLength, yOffset, 0), p.color, "%i/%i", p.frags, p.owned);
|
||||
yOffset += _voxelFontRender.lineHeight();
|
||||
}
|
||||
}
|
||||
|
||||
_voxelFontRender.swapBuffers();
|
||||
_voxelFontRender.render();
|
||||
}
|
||||
|
||||
TEST_APP(TestTraze)
|
|
@ -1,76 +0,0 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "testcore/TestApp.h"
|
||||
#include "voxelrender/RawVolumeRenderer.h"
|
||||
#include "voxelrender/VoxelFontRenderer.h"
|
||||
#include "core/EventBus.h"
|
||||
#include "audio/SoundManager.h"
|
||||
|
||||
#include "TrazeEvents.h"
|
||||
#include "TrazeProtocol.h"
|
||||
|
||||
#include "util/MessageQueue.h"
|
||||
|
||||
/**
|
||||
* @brief Example application that renders the state of a traze board. See https://traze.iteratec.de/ for more details.
|
||||
*/
|
||||
class TestTraze : public TestApp,
|
||||
public core::IEventBusHandler<traze::NewGridEvent>,
|
||||
public core::IEventBusHandler<traze::PlayerListEvent>,
|
||||
public core::IEventBusHandler<traze::TickerEvent>,
|
||||
public core::IEventBusHandler<traze::SpawnEvent>,
|
||||
public core::IEventBusHandler<traze::BikeEvent>,
|
||||
public core::IEventBusHandler<traze::ScoreEvent>,
|
||||
public core::IEventBusHandler<traze::NewGamesEvent> {
|
||||
private:
|
||||
using Super = TestApp;
|
||||
|
||||
core::VarPtr _name;
|
||||
|
||||
traze::Protocol _protocol;
|
||||
voxelrender::RawVolumeRenderer _rawVolumeRenderer;
|
||||
voxelrender::VoxelFontRenderer _voxelFontRender;
|
||||
MessageQueue _messageQueue;
|
||||
audio::SoundManager _soundMgr;
|
||||
|
||||
bool _renderBoard = true;
|
||||
bool _renderPlayerNames = true;
|
||||
|
||||
glm::ivec2 _spawnPosition { 0 };
|
||||
double _spawnTime = 0.0;
|
||||
|
||||
int _maxLength = 200;
|
||||
video::Camera _textCamera;
|
||||
|
||||
std::vector<traze::GameInfo> _games;
|
||||
std::vector<traze::Player> _players;
|
||||
int8_t _currentGameIndex = -1;
|
||||
double _nextConnectTime = 0.0;
|
||||
|
||||
void doRender() override;
|
||||
|
||||
void sound(const char *soundId);
|
||||
|
||||
const core::String& playerName(traze::PlayerId playerId) const;
|
||||
const traze::Player& player(traze::PlayerId playerId) const;
|
||||
public:
|
||||
TestTraze(const metric::MetricPtr& metric, const io::FilesystemPtr& filesystem, const core::EventBusPtr& eventBus, const core::TimeProviderPtr& timeProvider);
|
||||
|
||||
void onEvent(const traze::BikeEvent& event) override;
|
||||
void onEvent(const traze::NewGamesEvent& event) override;
|
||||
void onEvent(const traze::TickerEvent& event) override;
|
||||
void onEvent(const traze::SpawnEvent& event) override;
|
||||
void onEvent(const traze::NewGridEvent& event) override;
|
||||
void onEvent(const traze::PlayerListEvent& event) override;
|
||||
void onEvent(const traze::ScoreEvent& event) override;
|
||||
|
||||
virtual void onRenderUI() override;
|
||||
virtual app::AppState onConstruct() override;
|
||||
virtual app::AppState onInit() override;
|
||||
virtual app::AppState onRunning() override;
|
||||
virtual app::AppState onCleanup() override;
|
||||
};
|
|
@ -1,23 +0,0 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/EventBus.h"
|
||||
#include "TrazeTypes.h"
|
||||
#include "voxel/RawVolume.h"
|
||||
#include "core/SharedPtr.h"
|
||||
#include <vector>
|
||||
|
||||
namespace traze {
|
||||
|
||||
EVENTBUSPAYLOADEVENT(NewGridEvent, core::SharedPtr<voxel::RawVolume>);
|
||||
EVENTBUSPAYLOADEVENT(NewGamesEvent, std::vector<GameInfo>);
|
||||
EVENTBUSPAYLOADEVENT(PlayerListEvent, std::vector<Player>);
|
||||
EVENTBUSPAYLOADEVENT(TickerEvent, Ticker);
|
||||
EVENTBUSPAYLOADEVENT(SpawnEvent, Spawn);
|
||||
EVENTBUSPAYLOADEVENT(BikeEvent, Bike);
|
||||
EVENTBUSPAYLOADEVENT(ScoreEvent, Score);
|
||||
|
||||
}
|
|
@ -1,395 +0,0 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#include "JSON.h"
|
||||
#include "core/Log.h"
|
||||
#include "core/Color.h"
|
||||
#include "uuidutil/UUIDUtil.h"
|
||||
#include "core/Var.h"
|
||||
#include "app/App.h"
|
||||
#include "core/StringUtil.h"
|
||||
#include "TrazeTypes.h"
|
||||
#include "TrazeEvents.h"
|
||||
#include "TrazeProtocol.h"
|
||||
#include "voxel/Region.h"
|
||||
#include "voxel/MaterialColor.h"
|
||||
#include "voxel/RawVolume.h"
|
||||
#include "voxel/Voxel.h"
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
namespace traze {
|
||||
|
||||
Protocol::Protocol(const core::EventBusPtr& eventBus) :
|
||||
_eventBus(eventBus) {
|
||||
}
|
||||
|
||||
bool Protocol::init() {
|
||||
if (mosquitto_lib_init() != MOSQ_ERR_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_clientToken = uuid::generateUUID(); // + "_" + app::App::getInstance()->appname();
|
||||
Log::debug("Client token: %s", _clientToken.c_str());
|
||||
|
||||
_mosquitto = mosquitto_new(_clientToken.c_str(), true, this);
|
||||
if (_mosquitto == nullptr) {
|
||||
Log::error("Failed to create mosquitto instance");
|
||||
return false;
|
||||
}
|
||||
|
||||
mosquitto_message_callback_set(_mosquitto, [] (struct mosquitto *, void *userdata, const struct mosquitto_message *msg) {
|
||||
((Protocol*)userdata)->onMessage(msg);
|
||||
});
|
||||
mosquitto_connect_callback_set(_mosquitto, [] (struct mosquitto *, void *userdata, int rc) {
|
||||
uint8_t s = (uint8_t)rc;
|
||||
ConnectState state = (ConnectState)s;
|
||||
if (s > ConnectState::MaxKnown) {
|
||||
state = ConnectState::Unknown;
|
||||
}
|
||||
((Protocol*)userdata)->onConnect(state);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Protocol::connect() {
|
||||
if (_connected) {
|
||||
return true;
|
||||
}
|
||||
const char *host = core::Var::getSafe("mosquitto_host")->strVal().c_str();
|
||||
const int port = core::Var::getSafe("mosquitto_port")->intVal();
|
||||
Log::info("Trying to connect to %s at port %i...", host, port);
|
||||
const int rc = mosquitto_connect_async(_mosquitto, host, port, 60);
|
||||
if (rc != MOSQ_ERR_SUCCESS) {
|
||||
Log::error("Failed to connect to the mqtt broker %s", mosquitto_strerror(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
mosquitto_loop_start(_mosquitto);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Protocol::shutdown() {
|
||||
if (_mosquitto) {
|
||||
mosquitto_disconnect(_mosquitto);
|
||||
mosquitto_loop_stop(_mosquitto, false);
|
||||
mosquitto_destroy(_mosquitto);
|
||||
_mosquitto = nullptr;
|
||||
}
|
||||
mosquitto_lib_cleanup();
|
||||
}
|
||||
|
||||
bool Protocol::unsubscribe() {
|
||||
bool unsubscribed = true;
|
||||
for (const char* topic : {"traze/+/grid", "traze/+/players", "traze/+/ticker", "traze/+/scores"}) {
|
||||
const int rc = mosquitto_unsubscribe(_mosquitto, nullptr, topic);
|
||||
if (rc != MOSQ_ERR_SUCCESS) {
|
||||
Log::warn("Failed to unsubscribe from topic %s with error %s", topic, mosquitto_strerror(rc));
|
||||
unsubscribed = false;
|
||||
} else {
|
||||
Log::debug("Unsubscribed from topic %s", topic);
|
||||
}
|
||||
}
|
||||
_subscribed = false;
|
||||
_instanceName = "";
|
||||
return unsubscribed;
|
||||
}
|
||||
|
||||
bool Protocol::subscribe(const GameInfo& game) {
|
||||
if (_subscribed) {
|
||||
return true;
|
||||
}
|
||||
bool subscribed = true;
|
||||
const core::String& privateChannel = core::string::format("player/%s", _clientToken.c_str());
|
||||
for (const char* topic : {"grid", "players", "ticker", "scores", privateChannel.c_str()}) {
|
||||
char topicBuf[128];
|
||||
SDL_snprintf(topicBuf, sizeof(topicBuf), "traze/%s/%s", game.name.c_str(), topic);
|
||||
const int rc = mosquitto_subscribe(_mosquitto, nullptr, topicBuf, 0);
|
||||
if (rc != MOSQ_ERR_SUCCESS) {
|
||||
subscribed = false;
|
||||
Log::warn("Failed to subscribe to topic '%s' with error %s", topicBuf, mosquitto_strerror(rc));
|
||||
} else {
|
||||
Log::info("Subscribed to topic '%s'", topicBuf);
|
||||
}
|
||||
}
|
||||
_subscribed = subscribed;
|
||||
if (!_subscribed) {
|
||||
unsubscribe();
|
||||
} else {
|
||||
_instanceName = game.name;
|
||||
}
|
||||
|
||||
return subscribed;
|
||||
}
|
||||
|
||||
bool Protocol::send(const core::String& topic, const core::String& json) const {
|
||||
const int rc = mosquitto_publish(_mosquitto, nullptr, topic.c_str(), json.size(), json.c_str(), 0, true);
|
||||
if (rc != MOSQ_ERR_SUCCESS) {
|
||||
Log::warn("Failed to send to topic '%s' with error %s", topic.c_str(), mosquitto_strerror(rc));
|
||||
} else {
|
||||
Log::debug("Sent to topic '%s' with payload '%s'", topic.c_str(), json.c_str());
|
||||
}
|
||||
return rc == MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
bool Protocol::join(const core::String& name) {
|
||||
if (core::string::contains(name, "#")
|
||||
|| core::string::contains(name, "/")
|
||||
|| core::string::contains(name, "+")) {
|
||||
Log::warn("Illegal client name");
|
||||
return false;
|
||||
}
|
||||
core::json j;
|
||||
j["name"] = name.c_str();
|
||||
j["mqttClientName"] = _clientToken.c_str();
|
||||
Log::info("Trying to join the game %s with client token %s and name %s",
|
||||
_instanceName.c_str(), _clientToken.c_str(), name.c_str());
|
||||
const std::string& dump = j.dump();
|
||||
return send(core::string::format("traze/%s/join", _instanceName.c_str()), dump.c_str());
|
||||
}
|
||||
|
||||
bool Protocol::steer(BikeDirection direction) const {
|
||||
if (_playerId == 0) {
|
||||
Log::info("Not joined");
|
||||
return false;
|
||||
}
|
||||
const char *course = "N";
|
||||
if (direction == BikeDirection::E) {
|
||||
course = "E";
|
||||
} else if (direction == BikeDirection::S) {
|
||||
course = "S";
|
||||
} else if (direction == BikeDirection::W) {
|
||||
course = "W";
|
||||
}
|
||||
core::json j;
|
||||
j["course"] = course;
|
||||
j["playerToken"] = _playerToken.c_str();
|
||||
const std::string& dump = j.dump();
|
||||
return send(core::string::format("traze/%s/%i/steer", _instanceName.c_str(), _playerId), dump.c_str());
|
||||
}
|
||||
|
||||
bool Protocol::bail() {
|
||||
if (_playerId == 0) {
|
||||
Log::info("Not joined");
|
||||
return false;
|
||||
}
|
||||
core::json j;
|
||||
j["playerToken"] = _playerToken.c_str();
|
||||
const std::string& dump = j.dump();
|
||||
if (send(core::string::format("traze/%s/%i/bail", _instanceName.c_str(), _playerId), dump.c_str())) {
|
||||
_playerId = 0;
|
||||
_playerToken = "";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Protocol::parseOwnPlayer(const core::String& json) {
|
||||
const core::json j = core::json::parse(json);
|
||||
_playerToken = j["secretUserToken"].get<std::string>().c_str();
|
||||
_playerId = j["id"].get<int>();
|
||||
const glm::ivec2& position = j["position"];
|
||||
Log::info("Player token %s with id %u at pos %i:%i", _playerToken.c_str(), _playerId, position.x, position.y);
|
||||
_eventBus->enqueue(std::make_shared<SpawnEvent>(Spawn{position, true}));
|
||||
}
|
||||
|
||||
void Protocol::parsePlayers(const core::String& json) {
|
||||
const core::json j = core::json::parse(json);
|
||||
std::vector<Player> players;
|
||||
players.reserve(j.size());
|
||||
const voxel::MaterialColorArray& materialColors = voxel::getMaterialColors();
|
||||
for (const auto& player : j) {
|
||||
Player p;
|
||||
p.name = player["name"].get<std::string>().c_str();
|
||||
const core::String hex(player["color"].get<std::string>().c_str());
|
||||
const glm::vec4& color = core::Color::fromHex(hex.c_str());
|
||||
const uint8_t index = core::Color::getClosestMatch(color, materialColors);
|
||||
p.colorIndex = index;
|
||||
p.color = materialColors[index];
|
||||
p.id = player["id"].get<int>();
|
||||
p.frags = player["frags"].get<int>();
|
||||
p.owned = player["owned"].get<int>();
|
||||
players.push_back(p);
|
||||
Log::debug("Player %s with id %i", p.name.c_str(), p.id);
|
||||
}
|
||||
_eventBus->enqueue(std::make_shared<PlayerListEvent>(players));
|
||||
std::unordered_map<uint32_t, Player> playerMap;
|
||||
for (const auto& p : players) {
|
||||
playerMap[p.id] = p;
|
||||
}
|
||||
_players = playerMap;
|
||||
}
|
||||
|
||||
void Protocol::parseTicker(const core::String& json) const {
|
||||
const core::json j = core::json::parse(json);
|
||||
Ticker ticker;
|
||||
const core::String& type = j["type"].get<std::string>().c_str();
|
||||
if (type == "suicide") {
|
||||
ticker.type = TickerType::Suicide;
|
||||
} else if (type == "frag") {
|
||||
ticker.type = TickerType::Frag;
|
||||
} else if (type == "collision") {
|
||||
ticker.type = TickerType::Collision;
|
||||
} else {
|
||||
ticker.type = TickerType::Unknown;
|
||||
}
|
||||
ticker.casualty = j["casualty"].get<int>();
|
||||
ticker.fragger = j["fragger"].get<int>();
|
||||
_eventBus->enqueue(std::make_shared<TickerEvent>(ticker));
|
||||
}
|
||||
|
||||
void Protocol::parseGames(const core::String& json) const {
|
||||
const core::json j = core::json::parse(json);
|
||||
const size_t size = j.size();
|
||||
if (size == 0) {
|
||||
Log::debug("No active game found");
|
||||
return;
|
||||
}
|
||||
Log::debug("%i active games found", (int)size);
|
||||
std::vector<GameInfo> games;
|
||||
games.reserve(size);
|
||||
for (auto& it : j) {
|
||||
GameInfo g;
|
||||
g.activePlayers = it["activePlayers"];
|
||||
g.name = it["name"].get<std::string>().c_str();
|
||||
games.push_back(g);
|
||||
Log::debug("%s with %i players", g.name.c_str(), g.activePlayers);
|
||||
}
|
||||
_eventBus->enqueue(std::make_shared<NewGamesEvent>(games));
|
||||
}
|
||||
|
||||
void Protocol::parseScores(const core::String& json) {
|
||||
const core::json j = core::json::parse(json);
|
||||
using _S = std::pair<int, core::String>;
|
||||
std::vector<_S> entries;
|
||||
for (const auto &score : j.items()) {
|
||||
const int rank = score.value().get<int>();
|
||||
entries.emplace_back(_S{rank, score.key().c_str()});
|
||||
}
|
||||
if (entries.empty()) {
|
||||
return;
|
||||
}
|
||||
std::sort(entries.begin(), entries.end(), [] (const _S& s1, const _S& s2) { return s1.first < s2.first; });
|
||||
Score scores;
|
||||
scores.reserve(entries.size());
|
||||
for (const auto& e : entries) {
|
||||
scores.push_back(e.second);
|
||||
}
|
||||
_eventBus->enqueue(std::make_shared<ScoreEvent>(scores));
|
||||
}
|
||||
|
||||
void Protocol::parseGridAndUpdateVolume(const core::String& json) {
|
||||
const core::json j = core::json::parse(json);
|
||||
const int height = j["height"].get<int>();
|
||||
const int width = j["width"].get<int>();
|
||||
// x and z and swapped here
|
||||
const voxel::Region region(glm::ivec3(-1), glm::ivec3(height, 1, width));
|
||||
core::SharedPtr<voxel::RawVolume> v = core::make_shared<voxel::RawVolume>(region);
|
||||
const auto& grid = j["tiles"];
|
||||
int x = 0;
|
||||
for (const auto& line : grid) {
|
||||
if (x >= width) {
|
||||
Log::warn("Width overflow detected");
|
||||
break;
|
||||
}
|
||||
int z = 0;
|
||||
for (const auto& voxel : line) {
|
||||
if (z >= height) {
|
||||
Log::warn("Height overflow detected");
|
||||
break;
|
||||
}
|
||||
const int data = voxel.get<int>();
|
||||
if (data != 0) {
|
||||
const auto& iter = _players.find(data);
|
||||
if (iter == _players.end()) {
|
||||
Log::debug("Can't find grid player id %i in player list", data);
|
||||
continue;
|
||||
}
|
||||
v->setVoxel(glm::ivec3(z, 1, x), voxel::createColorVoxel(voxel::VoxelType::Generic, iter->second.colorIndex));
|
||||
}
|
||||
++z;
|
||||
}
|
||||
++x;
|
||||
}
|
||||
if (j.find("bikes") != j.end()) {
|
||||
const auto& bikes = j["bikes"];
|
||||
for (const auto& bike : bikes) {
|
||||
Bike b;
|
||||
b.playerId = bike["playerId"].get<int>();
|
||||
int locX = bike["currentLocation"][0].get<int>();
|
||||
int locY = bike["currentLocation"][1].get<int>();
|
||||
b.currentLocation = glm::ivec2(locX, locY);
|
||||
const core::String direction = bike["direction"].get<std::string>().c_str();
|
||||
if (direction == "W") {
|
||||
b.direction = BikeDirection::W;
|
||||
} else if (direction == "E") {
|
||||
b.direction = BikeDirection::E;
|
||||
} else if (direction == "N") {
|
||||
b.direction = BikeDirection::N;
|
||||
} else {
|
||||
b.direction = BikeDirection::S;
|
||||
}
|
||||
// TODO: "trail":[[2,0],[2,1]]
|
||||
_eventBus->enqueue(std::make_shared<BikeEvent>(b));
|
||||
}
|
||||
}
|
||||
if (j.find("spawns") != j.end()) {
|
||||
const auto& spawns = j["spawns"];
|
||||
for (const auto& spawn : spawns) {
|
||||
const glm::ivec2 spawnPos(spawn[0].get<int>(), spawn[1].get<int>());
|
||||
_eventBus->enqueue(std::make_shared<SpawnEvent>(Spawn{spawnPos, false}));
|
||||
}
|
||||
}
|
||||
_eventBus->enqueue(std::make_shared<NewGridEvent>(v));
|
||||
}
|
||||
|
||||
void Protocol::onMessage(const struct mosquitto_message *msg) {
|
||||
Log::debug("MQTT: received message with topic: '%s'", msg->topic);
|
||||
if (!msg->payloadlen) {
|
||||
Log::debug("MQTT: empty message - no payload");
|
||||
return;
|
||||
}
|
||||
const char *payload = (const char*)msg->payload;
|
||||
const core::String p(payload, msg->payloadlen);
|
||||
Log::debug("MQTT: received message with payload: '%s'", p.c_str());
|
||||
const char *subTopic = core::string::after(msg->topic, '/');
|
||||
if (!SDL_strcmp(subTopic, "games")) {
|
||||
parseGames(p);
|
||||
} else if (!SDL_strcmp(subTopic, "grid")) {
|
||||
parseGridAndUpdateVolume(p);
|
||||
} else if (!SDL_strcmp(subTopic, "players")) {
|
||||
parsePlayers(p);
|
||||
} else if (!SDL_strcmp(subTopic, "ticker")) {
|
||||
parseTicker(p);
|
||||
} else if (!SDL_strcmp(subTopic, "scores")) {
|
||||
parseScores(p);
|
||||
} else if (!SDL_strcmp(subTopic, _clientToken.c_str())) {
|
||||
parseOwnPlayer(p);
|
||||
} else {
|
||||
Log::error("Unknown message for topic %s", msg->topic);
|
||||
}
|
||||
}
|
||||
|
||||
void Protocol::onConnect(ConnectState status) {
|
||||
if (status != ConnectState::Success) {
|
||||
_connected = false;
|
||||
Log::error("Failed to connect to mqtt broker: %i", (int)status);
|
||||
return;
|
||||
}
|
||||
Log::info("Connected - subscribing now...");
|
||||
_connected = true;
|
||||
_subscribed = false;
|
||||
for (const char* topic : {"traze/games"}) {
|
||||
const int rc = mosquitto_subscribe(_mosquitto, nullptr, topic, 0);
|
||||
if (rc != MOSQ_ERR_SUCCESS) {
|
||||
Log::warn("Failed to subscribe to topic %s with error %s", topic, mosquitto_strerror(rc));
|
||||
} else {
|
||||
Log::debug("Subscribed to topic %s", topic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/EventBus.h"
|
||||
#include "voxel/RawVolume.h"
|
||||
#include <mosquitto.h>
|
||||
#include "core/String.h"
|
||||
#include "TrazeTypes.h"
|
||||
#include <unordered_map>
|
||||
|
||||
namespace traze {
|
||||
|
||||
class Protocol {
|
||||
private:
|
||||
core::EventBusPtr _eventBus;
|
||||
|
||||
core::String _instanceName;
|
||||
core::String _playerToken;
|
||||
core::String _clientToken;
|
||||
PlayerId _playerId = 0u;
|
||||
struct mosquitto *_mosquitto = nullptr;
|
||||
bool _connected = false;
|
||||
bool _subscribed = false;
|
||||
std::unordered_map<uint32_t, Player> _players;
|
||||
|
||||
enum ConnectState : uint8_t {
|
||||
Success = 0,
|
||||
UnacceptableProtocolVersion = 1,
|
||||
IdentifierRejected = 2,
|
||||
BrokerUnavailable = 3,
|
||||
MaxKnown = BrokerUnavailable,
|
||||
Max = 255,
|
||||
Unknown = 255
|
||||
};
|
||||
void onMessage(const struct mosquitto_message *msg);
|
||||
bool send(const core::String& topic, const core::String& json) const;
|
||||
|
||||
public:
|
||||
Protocol(const core::EventBusPtr& eventBus);
|
||||
|
||||
bool init();
|
||||
void shutdown();
|
||||
|
||||
bool connect();
|
||||
|
||||
PlayerId playerId() const;
|
||||
|
||||
bool joined() const;
|
||||
bool connected() const;
|
||||
|
||||
bool unsubscribe();
|
||||
bool subscribe(const GameInfo& games);
|
||||
|
||||
void onConnect(ConnectState status);
|
||||
|
||||
/**
|
||||
* @brief A high score table is published every 10 seconds at the scores topic.
|
||||
* @code
|
||||
* {
|
||||
* "ingameNick1[id1]": 238,
|
||||
* "ingameNick2[id2]": 235
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
void parseScores(const core::String& json);
|
||||
|
||||
/**
|
||||
* @brief The player topic is published every 5 seconds.
|
||||
* @code
|
||||
* [
|
||||
* {
|
||||
* "id": 1,
|
||||
* "name": "player1",
|
||||
* "color": "#28BA3C",
|
||||
* "frags": 1,
|
||||
* "owned": 2
|
||||
* },
|
||||
* {
|
||||
* "id": 2,
|
||||
* "name": "player2",
|
||||
* "color": "#0A94FF",
|
||||
* "frags": 2,
|
||||
* "owned": 1
|
||||
* }
|
||||
* ]
|
||||
* @endcode
|
||||
*/
|
||||
void parsePlayers(const core::String& json);
|
||||
|
||||
/**
|
||||
* @code
|
||||
* {
|
||||
* "id": 1337,
|
||||
* "name": "myIngameNick",
|
||||
* "secretUserToken":"de37c1bc-d0e6-4c66-aaa3-911511f43d54",
|
||||
* "position": [15,3]
|
||||
*}
|
||||
* @endcode
|
||||
*/
|
||||
void parseOwnPlayer(const core::String& json);
|
||||
|
||||
/**
|
||||
* @brief The ticker topic is published whenever a death of a player occurs.
|
||||
* @code
|
||||
* {
|
||||
* "type": "frag", (or suicide)
|
||||
* "casualty": 2,
|
||||
* "fragger": 4
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
void parseTicker(const core::String& json) const;
|
||||
|
||||
/**
|
||||
* @code
|
||||
* [
|
||||
* {
|
||||
* "name": "instanceName",
|
||||
* "activePlayers": 5
|
||||
* }
|
||||
* ]
|
||||
* @endcode
|
||||
*/
|
||||
void parseGames(const core::String& json) const;
|
||||
|
||||
/**
|
||||
* @brief The grid topic is published on every server tick. (4 times a Second)
|
||||
* @code
|
||||
* {
|
||||
* "height":3,
|
||||
* "width":3,
|
||||
* "tiles":[
|
||||
* [ 1, 0, 0 ],
|
||||
* [ 1, 1, 2 ],
|
||||
* [ 0, 2, 2 ]
|
||||
* ],
|
||||
* "bikes":[
|
||||
* {
|
||||
* "playerId":2,
|
||||
* "currentLocation":[1,0],
|
||||
* "direction":"W",
|
||||
* "trail":[[2,0],[2,1]]
|
||||
* }
|
||||
* ],
|
||||
* "spawns":[[2,2]]
|
||||
*}
|
||||
* @endcode
|
||||
*/
|
||||
void parseGridAndUpdateVolume(const core::String& json);
|
||||
|
||||
/**
|
||||
* @brief Client Registration
|
||||
* You send a request to join the game. In return you'll get a user token that allows you to control
|
||||
* your bike. The Response will be sent to your private MQTT topic.
|
||||
*
|
||||
* traze/{instanceName}/join
|
||||
* @code
|
||||
* {
|
||||
* "name": "myIngameNick",
|
||||
* "mqttClientName": "myClientName"
|
||||
* }
|
||||
* @endcode
|
||||
* If the server accepts your request you'll receive a message communicating your initial position. Once you give your first direction command your game starts.
|
||||
* You have to provide a unique MQTT client name in order to receive your session token on your clients own topic. It is important that you specify this very
|
||||
* client name in the MQTT connect call to the broker, otherwise you will not be able to receive messages on the traze/{instanceName}/player/{myClientName} topic
|
||||
* due to the brokers access control list settings. In order to not be subject to a MQTT deauthentication attack you should choose a client name that can not be
|
||||
* guessed. UUIDs are a good solution.
|
||||
*
|
||||
* traze/{instanceName}/player/{myClientName}
|
||||
* @code
|
||||
* {
|
||||
* "id": 1337,
|
||||
* "name": "myIngameNick",
|
||||
* "secretUserToken":"de37c1bc-d0e6-4c66-aaa3-911511f43d54",
|
||||
* "position": [15,3]
|
||||
* }
|
||||
* @endcode
|
||||
* Because the ingame nick is part of the topic your nickname may not include #, +, /.
|
||||
*/
|
||||
bool join(const core::String& name);
|
||||
|
||||
/**
|
||||
* @brief Steering your Light Cycle
|
||||
* You steer by giving the directions for your next turn via an MQTT message. If you don't commit a course correction within the specified timeframe your light cycle will continue on it's previous path.
|
||||
*
|
||||
* traze/{instanceName}/{playerId}/steer
|
||||
* @code
|
||||
* {
|
||||
* "course":"N",
|
||||
* "playerToken": "de37c1bc-d0e6-4c66-aaa3-911511f43d54"
|
||||
* }
|
||||
* @endcode
|
||||
* The options for a course Change are North, South, East or West.
|
||||
*/
|
||||
bool steer(BikeDirection direction) const;
|
||||
|
||||
/**
|
||||
* @brief Leaving a Game
|
||||
*
|
||||
* You may leave the game at any time.
|
||||
*
|
||||
* traze/{instanceName}/{playerId}/bail
|
||||
* @code
|
||||
* "playerToken": "yourSecretToken"
|
||||
* @endcode
|
||||
*/
|
||||
bool bail();
|
||||
};
|
||||
|
||||
inline PlayerId Protocol::playerId() const {
|
||||
return _playerId;
|
||||
}
|
||||
|
||||
inline bool Protocol::joined() const {
|
||||
return _playerId != 0u;
|
||||
}
|
||||
|
||||
inline bool Protocol::connected() const {
|
||||
return _connected;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "core/String.h"
|
||||
#include <stdint.h>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
namespace traze {
|
||||
|
||||
struct GameInfo {
|
||||
core::String name;
|
||||
int activePlayers;
|
||||
};
|
||||
|
||||
enum class BikeDirection {
|
||||
N, S, W, E
|
||||
};
|
||||
|
||||
using PlayerId = uint32_t;
|
||||
|
||||
using Score = std::vector<core::String>;
|
||||
|
||||
struct Spawn {
|
||||
glm::ivec2 position;
|
||||
bool own;
|
||||
};
|
||||
|
||||
struct Bike {
|
||||
PlayerId playerId;
|
||||
glm::ivec2 currentLocation;
|
||||
BikeDirection direction;
|
||||
};
|
||||
|
||||
struct Player {
|
||||
core::String name;
|
||||
PlayerId id = 0u;
|
||||
uint32_t frags = 0u;
|
||||
uint32_t owned = 0u;
|
||||
uint8_t colorIndex = 0u;
|
||||
glm::vec4 color {0.0f};
|
||||
};
|
||||
|
||||
enum class TickerType {
|
||||
Frag, Suicide, Collision, Unknown
|
||||
};
|
||||
|
||||
struct Ticker {
|
||||
TickerType type = TickerType::Unknown;
|
||||
int casualty;
|
||||
PlayerId fragger;
|
||||
};
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,91 +0,0 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#include "app/tests/AbstractTest.h"
|
||||
#include "../TrazeProtocol.h"
|
||||
#include "../TrazeEvents.h"
|
||||
|
||||
namespace traze {
|
||||
|
||||
class TrazeProtocolTest: public app::AbstractTest,
|
||||
public core::IEventBusHandler<traze::NewGridEvent>,
|
||||
public core::IEventBusHandler<traze::PlayerListEvent>,
|
||||
public core::IEventBusHandler<traze::TickerEvent>,
|
||||
public core::IEventBusHandler<traze::SpawnEvent>,
|
||||
public core::IEventBusHandler<traze::BikeEvent>,
|
||||
public core::IEventBusHandler<traze::ScoreEvent>,
|
||||
public core::IEventBusHandler<traze::NewGamesEvent> {
|
||||
private:
|
||||
using Super = app::AbstractTest;
|
||||
protected:
|
||||
Protocol *_p;
|
||||
core::EventBusPtr _eventBus;
|
||||
Score _score;
|
||||
|
||||
public:
|
||||
void SetUp() override {
|
||||
Super::SetUp();
|
||||
_eventBus = app::App::getInstance()->eventBus();
|
||||
_eventBus->subscribe<traze::NewGridEvent>(*this);
|
||||
_eventBus->subscribe<traze::NewGamesEvent>(*this);
|
||||
_eventBus->subscribe<traze::PlayerListEvent>(*this);
|
||||
_eventBus->subscribe<traze::TickerEvent>(*this);
|
||||
_eventBus->subscribe<traze::SpawnEvent>(*this);
|
||||
_eventBus->subscribe<traze::BikeEvent>(*this);
|
||||
_eventBus->subscribe<traze::ScoreEvent>(*this);
|
||||
_p = new Protocol(_eventBus) ;
|
||||
ASSERT_TRUE(_p->init()) << "Initialization failed";
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
_eventBus->unsubscribe<traze::NewGridEvent>(*this);
|
||||
_eventBus->unsubscribe<traze::NewGamesEvent>(*this);
|
||||
_eventBus->unsubscribe<traze::PlayerListEvent>(*this);
|
||||
_eventBus->unsubscribe<traze::TickerEvent>(*this);
|
||||
_eventBus->unsubscribe<traze::SpawnEvent>(*this);
|
||||
_eventBus->unsubscribe<traze::BikeEvent>(*this);
|
||||
_eventBus->unsubscribe<traze::ScoreEvent>(*this);
|
||||
_p->shutdown();
|
||||
delete _p;
|
||||
_p = nullptr;
|
||||
Super::TearDown();
|
||||
}
|
||||
|
||||
void onEvent(const traze::BikeEvent& event) override {
|
||||
}
|
||||
|
||||
void onEvent(const traze::NewGamesEvent& event) override {
|
||||
}
|
||||
|
||||
void onEvent(const traze::TickerEvent& event) override {
|
||||
}
|
||||
|
||||
void onEvent(const traze::SpawnEvent& event) override {
|
||||
}
|
||||
|
||||
void onEvent(const traze::NewGridEvent& event) override {
|
||||
}
|
||||
|
||||
void onEvent(const traze::PlayerListEvent& event) override {
|
||||
}
|
||||
|
||||
void onEvent(const traze::ScoreEvent& event) override {
|
||||
_score = event.get();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(TrazeProtocolTest, testParseScores) {
|
||||
const core::String json = R"(
|
||||
{
|
||||
"ingameNick1[id1]": 238,
|
||||
"ingameNick2[id2]": 235
|
||||
})";
|
||||
_p->parseScores(json);
|
||||
ASSERT_EQ(0, _eventBus->update());
|
||||
ASSERT_EQ(2, (int)_score.size());
|
||||
EXPECT_EQ("ingameNick2[id2]", _score[0]);
|
||||
EXPECT_EQ("ingameNick1[id1]", _score[1]);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue