TRAZE: removed traze client

looks like traze is no longer available at https://traze.iteratec.de/
master
Martin Gerhardy 2021-11-21 22:00:55 +01:00
parent fdee3de225
commit f5d7b190d7
18 changed files with 4 additions and 20301 deletions

View File

@ -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$

View File

@ -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

View File

@ -1,2 +0,0 @@
include("${ROOT_DIR}/cmake/macros.cmake")
engine_find(mosquitto mosquitto.h "" "" "")

View File

@ -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
```

View File

@ -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

View File

@ -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.

View File

@ -46,5 +46,4 @@ nav:
- AI development: AIRemoteDebugger.md
- Test applications:
- TestAnimation: TestAnimation.md
- Traze: Traze.md
- VisualTests.md

View File

@ -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)

View File

@ -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()

View File

@ -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>();
}
}

View File

@ -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)

View File

@ -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;
};

View File

@ -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);
}

View File

@ -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);
}
}
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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]);
}
}