/** * @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 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(*this); _eventBus->subscribe(*this); _eventBus->subscribe(*this); _eventBus->subscribe(*this); _eventBus->subscribe(*this); _eventBus->subscribe(*this); _eventBus->subscribe(*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 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& 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)