commit ddfafb89b7ae75ce7638e29388838af5862a654b Author: Nekojimi Date: Sat Apr 6 18:23:28 2024 +0100 Initial commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..857d54d --- /dev/null +++ b/.gitignore @@ -0,0 +1,76 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* +CMakeLists.txt.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + +# Build directories +build* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1a95528 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,136 @@ +cmake_minimum_required(VERSION 3.14) + +project(EuroFuckSimulator LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +#add_compile_options(-fsanitize=address) +#add_link_options(-fsanitize=address) + +include(ExternalProject) +include(FetchContent) + +#if (OPT_MINGW_STD_THREADS) +# FetchContent_Declare( +# mingw-std-threads +# GIT_REPOSITORY https://github.com/meganz/mingw-std-threads.git +# GIT_TAG master +# ) +# FetchContent_MakeAvailable(mingw-std-threads) +#endif() + +if (OPT_QT_JSON_SETTINGS) + FetchContent_Declare( + QtJsonSettings + GIT_REPOSITORY https://github.com/jimj316/QtJsonSettings.git + GIT_TAG main + ) + FetchContent_MakeAvailable(QtJsonSettings) + add_compile_definitions(WITH_QT_JSON_SETTINGS) +endif() + +find_package(ixwebsocket CONFIG REQUIRED) +find_package(nlohmann_json 3.2.0 REQUIRED) +find_package(ZLIB REQUIRED) +find_package(Threads REQUIRED) + +file(GLOB BUTTPLUG_SRC_FILES + "libraries/buttplugCpp/src/*.cpp" + "libraries/buttplugCpp/include/*.h" +) + +file(GLOB_RECURSE SCS_SDK_FILES + "libraries/scs-sdk/include/*.h" +) + +#file(GLOB ACCORDION_FILES +# "libraries/accordion/src/*" +#) + +find_package(QT NAMES Qt5 REQUIRED COMPONENTS Core Gui Widgets Charts LinguistTools) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui Widgets Charts LinguistTools) + +set(TS_FILES EuroFuckSimulator_en_GB.ts) + +include_directories( +# libraries/accordion/src/ + libraries/buttplugCpp/include/ + libraries/scs-sdk/include/ + src/ +) + +if (OPT_QT_JSON_SETTINGS) + include_directories( + ${QtJsonSettings_SOURCE_DIR} + ) +endif() + +set (SOURCE_FILES + src/EuroFuckSimulator_global.h + src/eurofucksimulator.cpp + src/eurofucksimulator.h + src/telemetry.h + src/qapphost.h + src/qapphost.cpp + src/rulemodule.h + src/rulemodule.cpp + + src/gui/gui.h + src/gui/gui.cpp + src/gui/gui.ui + src/gui/fancygraph.h + src/gui/fancygraph.cpp + src/gui/fancyslider.h + src/gui/fancyslider.cpp +) + +if (OPT_BUILD_TEST_EXE) +message("Building test executable.") +add_executable(EuroFuckSimulator + src/main.cpp + ${SOURCE_FILES} + + ${TS_FILES} + ${BUTTPLUG_SRC_FILES} + ${SCS_SDK_FILES} +# ${ACCORDION_FILES} +) +else() +add_library(EuroFuckSimulator SHARED + src/telemeter.cpp + ${SOURCE_FILES} + + ${TS_FILES} + ${BUTTPLUG_SRC_FILES} + ${SCS_SDK_FILES} +# ${ACCORDION_FILES} +) +endif() + +target_link_libraries(EuroFuckSimulator PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets + Qt${QT_VERSION_MAJOR}::Charts + ixwebsocket::ixwebsocket + nlohmann_json::nlohmann_json + +) + +if (OPT_QT_JSON_SETTINGS) + target_link_libraries(EuroFuckSimulator PRIVATE + QtJsonSettings + ) +endif() + +target_compile_definitions(EuroFuckSimulator PRIVATE EUROFUCKSIMULATOR_LIBRARY) + +if(COMMAND qt_create_translation) + qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) +else() + qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) +endif() diff --git a/EuroFuckSimulator_en_GB.ts b/EuroFuckSimulator_en_GB.ts new file mode 100644 index 0000000..76f52a4 --- /dev/null +++ b/EuroFuckSimulator_en_GB.ts @@ -0,0 +1,3 @@ + + + diff --git a/src/EuroFuckSimulator_global.h b/src/EuroFuckSimulator_global.h new file mode 100644 index 0000000..25666eb --- /dev/null +++ b/src/EuroFuckSimulator_global.h @@ -0,0 +1,12 @@ +#ifndef EUROFUCKSIMULATOR_GLOBAL_H +#define EUROFUCKSIMULATOR_GLOBAL_H + +#include + +#if defined(EUROFUCKSIMULATOR_LIBRARY) +# define EUROFUCKSIMULATOR_EXPORT Q_DECL_EXPORT +#else +# define EUROFUCKSIMULATOR_EXPORT Q_DECL_IMPORT +#endif + +#endif // EUROFUCKSIMULATOR_GLOBAL_H diff --git a/src/eurofucksimulator.cpp b/src/eurofucksimulator.cpp new file mode 100644 index 0000000..bad73b2 --- /dev/null +++ b/src/eurofucksimulator.cpp @@ -0,0 +1,16 @@ +#include "eurofucksimulator.h" + +#include +#include +#include + +#define _USE_MATH_DEFINES +#include + +EuroFuckSimulator::EuroFuckSimulator() + :QObject() +{ + + +} + diff --git a/src/eurofucksimulator.h b/src/eurofucksimulator.h new file mode 100644 index 0000000..0c3bfe4 --- /dev/null +++ b/src/eurofucksimulator.h @@ -0,0 +1,53 @@ +#ifndef EUROFUCKSIMULATOR_H +#define EUROFUCKSIMULATOR_H + +#include "EuroFuckSimulator_global.h" + +#include + +#include "telemetry.h" +#include "gui/gui.h" + +class EUROFUCKSIMULATOR_EXPORT EuroFuckSimulator: public QObject +{ + Q_OBJECT; +private: + + Gui* gui; + + + // speed module + double speedSineX = 0.0; + double speedMaxMPH = 70.0; + double speedWaveFreq = 2.5; + double speedWaveAmp = 0.25; + double speedWeight = 0.75; + + // rpm module + double rpmRate = 1500.0; // RPM for full power + double rpmWeight = 0.25; + + // punishment module + double punishmentTimeRate = 1.0 / 500.0; // punishment time (seconds) per euro fined + double punishmentMultiplier = 0.1; // + + // job reward module + double rewardTimeRate = 30.0 / 1500.0; // reward time (seconds) per euro earned + + // distance reward module +// double + + // crash module? + double crashTime = 0.5; // time the motor runs at full power after a crash + + // + + +public: + EuroFuckSimulator(); + ~EuroFuckSimulator() = default; + + +}; + +#endif // EUROFUCKSIMULATOR_H diff --git a/src/gui/fancygraph.cpp b/src/gui/fancygraph.cpp new file mode 100644 index 0000000..27dedbf --- /dev/null +++ b/src/gui/fancygraph.cpp @@ -0,0 +1,79 @@ +#include "fancygraph.h" +#include "qevent.h" + +#include +#include +#include +#include +#include + +FancyGraph::FancyGraph(QWidget *parent) + : QWidget{parent} +{ + timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, QOverload<>::of(&FancyGraph::repaint)); + timer->start(1000/60); +} + +QPointF FancyGraph::dataToScreen(QPointF dataPoint, QRectF paintZone) +{ + double time = QDateTime::currentMSecsSinceEpoch() / 1000.0; + + QPointF screenPoint; + + screenPoint.setX(paintZone.left() + (1-((time - dataPoint.x())/historyLength)) * paintZone.width()); + screenPoint.setY(paintZone.top() + (1-dataPoint.y()) * paintZone.height()); + + return screenPoint; +} + +void FancyGraph::paintEvent(QPaintEvent* event) +{ + QRectF paintZone = rect().adjusted(6,6,-6,-6); + QColor colour = QColor(217,0,74); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); +// painter.setRenderHint(QPainter::HighQualityAntialiasing); + painter.setClipRegion(event->region()); +// painter.begin(this);/ + + double time = QDateTime::currentMSecsSinceEpoch() / 1000.0; + + // trim data points from too far in the past + while (!points.empty() && points.first().x() < time - (historyLength * 1.1)) + { + points.removeFirst(); + } + + if (!points.empty()) + { + QPainterPath path; + path.moveTo(dataToScreen(points.first(), paintZone)); + for (QPointF point: points) + { + path.lineTo(dataToScreen(point, paintZone)); + } + +// path.addEllipse(end, 3, 3); + + painter.setPen(QPen(QBrush(colour), 2.5, Qt::PenStyle::SolidLine, Qt::PenCapStyle::RoundCap, Qt::RoundJoin)); + painter.drawPath(path); + + QPointF end = dataToScreen(points.last(), paintZone); + end.setX(paintZone.right()); + painter.setBrush(QBrush(colour)); + painter.drawEllipse(end, 3, 3); + } + +// painter.end(); +} + +void FancyGraph::addPoint(double value) +{ + if (!std::isnan(value)) + { + QPointF point(QDateTime::currentMSecsSinceEpoch() / 1000.0, value); + points.append(point); + } +} diff --git a/src/gui/fancygraph.h b/src/gui/fancygraph.h new file mode 100644 index 0000000..e76756c --- /dev/null +++ b/src/gui/fancygraph.h @@ -0,0 +1,34 @@ +#ifndef FANCYGRAPH_H +#define FANCYGRAPH_H + +#include +#include +#include +#include +#include + +class FancyGraph : public QWidget +{ + Q_OBJECT + +private: + QTimer* timer; + + QList points; + + double historyLength = 10.0; // secs + + QPointF dataToScreen(QPointF dataPoint, QRectF paintZone); +public: + explicit FancyGraph(QWidget *parent = nullptr); + + void paintEvent(QPaintEvent *event) override; + +public slots: + void addPoint(double value); + +signals: + +}; + +#endif // FANCYGRAPH_H diff --git a/src/gui/fancyslider.cpp b/src/gui/fancyslider.cpp new file mode 100644 index 0000000..78ac911 --- /dev/null +++ b/src/gui/fancyslider.cpp @@ -0,0 +1,56 @@ +#include "fancyslider.h" +#include "qboxlayout.h" + +#include + +FancySlider::FancySlider(QWidget *parent) + : QWidget{parent} +{ + QHBoxLayout* hbox = new QHBoxLayout(this); + hbox->setMargin(0); + hbox->setContentsMargins(0, 0, 0, 0); + setLayout(hbox); + + slider = new QSlider(); + slider->setOrientation(Qt::Orientation::Horizontal); + slider->setMaximum(sliderResolution); + + box = new QDoubleSpinBox(); + label = new QLabel(); + + layout()->addWidget(slider); + layout()->addWidget(box); + layout()->addWidget(label); + + connect(box, QOverload::of(&QDoubleSpinBox::valueChanged), this, &FancySlider::setValue); + connect(slider, &QSlider::valueChanged, this, [&](int v) + { + setValue(((v / (double)sliderResolution) * (max - min)) + min); + }); + + +} + +void FancySlider::setValue(double v) +{ + if (v != value) + { + value = v; + updateValues(); + + emit valueChanged(value); + } +} + +void FancySlider::updateValues() +{ + box->setMaximum(max); + box->setMinimum(min); + if (value != box->value()) + box->setValue(value); + + slider->setValue(((value - min) / (max - min)) * sliderResolution); + + box->setSingleStep((max-min) / sliderResolution); + box->setDecimals(std::ceil(-std::log10(box->singleStep()))); +} diff --git a/src/gui/fancyslider.h b/src/gui/fancyslider.h new file mode 100644 index 0000000..7341be9 --- /dev/null +++ b/src/gui/fancyslider.h @@ -0,0 +1,44 @@ +#ifndef FANCYSLIDER_H +#define FANCYSLIDER_H + +#include +#include +#include +#include + +class FancySlider : public QWidget +{ + Q_OBJECT; + + Q_PROPERTY(double value READ getValue WRITE setValue NOTIFY valueChanged); +public: + explicit FancySlider(QWidget *parent = nullptr); + + double getValue() {return value;} + double getMin() {return min;} + double getMax() {return max;} + +private: + QSlider* slider; + QDoubleSpinBox* box; + QLabel* label; + + void updateValues(); + + double value; + double min = 0.0; + double max = 100.0; + + int sliderResolution = 1000.0; + +public slots: + Q_SLOT void setValue(double value); + void setMin(double newMin) {min = newMin; updateValues();} + void setMax(double newMax) {max = newMax; updateValues();} + +signals: + void valueChanged(double value); + +}; + +#endif // FANCYSLIDER_H diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp new file mode 100644 index 0000000..65a3128 --- /dev/null +++ b/src/gui/gui.cpp @@ -0,0 +1,348 @@ +#include "gui.h" +#include "qdebug.h" +#include "ui_gui.h" + +#include "gui/fancyslider.h" + +#include +#include + +Gui::Gui(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::Gui) +{ + ui->setupUi(this); + + QScreen *screen = QGuiApplication::primaryScreen(); + QRect screenGeometry = screen->geometry(); + setGeometry(screenGeometry.right() - width(), screenGeometry.bottom() - height(), width(), height()); + + file = new QFile("efs.log"); + if (file->open(QIODevice::Truncate | QIODevice::WriteOnly)) + { + stream = new QTextStream(file); + } + else + { + throw new std::runtime_error("Failed to open log file."); + } + + logLine("Starting up!"); + + client = new Client("ws://127.0.0.1", 12345); + client->connect(callbackFunctionPtr); + client->requestDeviceList(); + + logLine("Client started!"); + + logLine("Started OK!"); + setWindowFlag(Qt::WindowStaysOnTopHint, true); +// setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | ); + + modules.append(&speedMod); + connect(ui->speedEnabledBox, &QCheckBox::stateChanged, &speedMod, &SpeedModule::setEnabled); + connect(&speedMod, &SpeedModule::EnabledChanged, ui->speedEnabledBox, &QCheckBox::setChecked); + connect(ui->speedTopSlider, &FancySlider::valueChanged, &speedMod, &SpeedModule::setTopSpeed); + connect(&speedMod, &SpeedModule::TopSpeedChanged, ui->speedTopSlider, &FancySlider::setValue); + ui->speedTopSlider->setMax(100.0); + connect(ui->speedBumpFreqSlider, &FancySlider::valueChanged, &speedMod, &SpeedModule::setWaveFrequency); + connect(ui->speedBumpFreqSlider, &FancySlider::valueChanged, this, [](double v){qDebug() << "Wave freq " << v;}); + connect(&speedMod, &SpeedModule::WaveFrequencyChanged, ui->speedBumpFreqSlider, &FancySlider::setValue); + ui->speedBumpFreqSlider->setMax(5.0); + ui->speedBumpFreqSlider->setMin(0.1); + connect(ui->speedBumpAmpSlider, &FancySlider::valueChanged, &speedMod, &SpeedModule::setWaveAmplitude); + connect(&speedMod, &SpeedModule::WaveAmplitudeChanged, ui->speedBumpAmpSlider, &FancySlider::setValue); + ui->speedBumpAmpSlider->setMax(0.5); + connect(ui->speedOutputPowerSlider, &FancySlider::valueChanged, &speedMod, &SpeedModule::setMaxOutput); + connect(&speedMod, &SpeedModule::MaxOutputChanged, ui->speedOutputPowerSlider, &FancySlider::setValue); + ui->speedOutputPowerSlider->setMax(1.0); + + modules.append(&rpmMod); + connect(ui->rpmEnabledBox, &QCheckBox::stateChanged, &rpmMod, &RPMModule::setEnabled); + connect(&rpmMod, &RPMModule::EnabledChanged, ui->rpmEnabledBox, &QCheckBox::setChecked); + connect(ui->rpmTopSlider, &FancySlider::valueChanged, &rpmMod, &RPMModule::setTopRPM); + connect(&rpmMod, &RPMModule::TopRPMChanged, ui->rpmTopSlider, &FancySlider::setValue); + ui->rpmTopSlider->setMax(3000.0); + connect(ui->rpmBottomSlider, &FancySlider::valueChanged, &rpmMod, &RPMModule::setBottomRPM); + connect(&rpmMod, &RPMModule::BottomRPMChanged, ui->rpmBottomSlider, &FancySlider::setValue); + ui->rpmBottomSlider->setMax(1000.0); + connect(ui->rpmOutputPowerSlider, &FancySlider::valueChanged, &rpmMod, &RPMModule::setMaxOutput); + connect(&rpmMod, &RPMModule::MaxOutputChanged, ui->rpmOutputPowerSlider, &FancySlider::setValue); + ui->rpmOutputPowerSlider->setMax(1.0); + + modules.append(&crimeMod); + connect(ui->crimeEnableBox, &QCheckBox::stateChanged, &crimeMod, &CrimeModule::setEnabled); + connect(&crimeMod, &CrimeModule::EnabledChanged, ui->crimeEnableBox, &QCheckBox::setChecked); + connect(ui->crimeRateSlider, &FancySlider::valueChanged, &crimeMod, &CrimeModule::setSecondsPerEuro); + connect(&crimeMod, &CrimeModule::SecondsPerEuroChanged, ui->crimeRateSlider, &FancySlider::setValue); + ui->crimeRateSlider->setMax(100.0); + connect(ui->crimePowerMultiplier, &FancySlider::valueChanged, &crimeMod, &CrimeModule::setPowerMultiplier); + connect(&crimeMod, &CrimeModule::PowerMultiplierChanged, ui->crimePowerMultiplier, &FancySlider::setValue); + ui->crimePowerMultiplier->setMax(2.0); + + modules.append(&jobMod); + connect(ui->jobRewardEnableBox, &QCheckBox::stateChanged, &jobMod, &JobModule::setEnabled); + connect(&jobMod, &JobModule::EnabledChanged, ui->jobRewardEnableBox, &QCheckBox::setChecked); + connect(ui->jobRewardMoneyRateSlider, &FancySlider::valueChanged, &jobMod, &JobModule::setSecondsPerEuro); + connect(&jobMod, &JobModule::SecondsPerEuroChanged, ui->jobRewardMoneyRateSlider, &FancySlider::setValue); + ui->jobRewardMoneyRateSlider->setMax(10); + connect(ui->jobRewardXPRateSlider, &FancySlider::valueChanged, &jobMod, &JobModule::setSecondsPerXP); + connect(&jobMod, &JobModule::SecondsPerXPChanged, ui->jobRewardXPRateSlider, &FancySlider::setValue); + ui->jobRewardXPRateSlider->setMax(30); + connect(ui->jobRewardPowerBoostSlider, &FancySlider::valueChanged, &jobMod, &JobModule::setPowerBoost); + connect(&jobMod, &JobModule::PowerBoostChanged, ui->jobRewardPowerBoostSlider, &FancySlider::setValue); + ui->jobRewardPowerBoostSlider->setMax(1.0 ); + + modules.append(&distMod); + connect(ui->distanceRewardEnabledBox, &QCheckBox::stateChanged, &distMod, &JobModule::setEnabled); + connect(&distMod, &JobModule::EnabledChanged, ui->distanceRewardEnabledBox, &QCheckBox::setChecked); + connect(ui->distanceRewardDistSlider, &FancySlider::valueChanged, &distMod, &DistanceModule::setDistance); + connect(&distMod, &DistanceModule::DistanceChanged, ui->distanceRewardDistSlider, &FancySlider::setValue); + ui->distanceRewardDistSlider->setMax(50); + connect(ui->distanceRewardRandSlider, &FancySlider::valueChanged, &distMod, &DistanceModule::setRandomness); + connect(&distMod, &DistanceModule::RandomnessChanged, ui->distanceRewardRandSlider, &FancySlider::setValue); + ui->distanceRewardRandSlider->setMax(1); + connect(ui->distanceRewardDurationSlider, &FancySlider::valueChanged, &distMod, &DistanceModule::setDuration); + connect(&distMod, &DistanceModule::DurationChanged, ui->distanceRewardDurationSlider, &FancySlider::setValue); + ui->distanceRewardDurationSlider->setMax(60.0); + connect(ui->distanceRewardPowerBoostSlider, &FancySlider::valueChanged, &distMod, &DistanceModule::setPowerBoost); + connect(&distMod, &DistanceModule::PowerBoostChanged, ui->distanceRewardPowerBoostSlider, &FancySlider::setValue); + ui->distanceRewardPowerBoostSlider->setMax(1.0); + + loadSettings(); + + for (RuleModule* module: modules) + { + connect(module, &RuleModule::statusUpdate, this, &Gui::rulesStatusUpdate); + } + + ui->tabWidget->setCurrentIndex(0); + + timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &Gui::update); + timer->start(UPDATE_INTERVAL_MS); +} + +Gui::~Gui() +{ + delete ui; +} + +//void Gui::doubleConnect(QObject* objectA, QObject* objectB, const QMetaMethod aSignal, const QMetaMethod aSlot, const QMetaMethod bSignal, const QMetaMethod bSlot ) +//{ +// connect(objectA, aSignal, objectB, bSlot); +// connect(objectB, bSignal, objectA, aSlot); +//} + +void Gui::loadSettings() +{ + + QSettings settings; + qDebug() << "Loading settings from " << settings.fileName(); + for (RuleModule* module : modules) + { + module->load(settings); + } +} + +void Gui::saveSettings() +{ + QSettings settings; + for (RuleModule* module : modules) + { + module->save(settings); + } + +} + +void Gui::shutdown() +{ + logLine("Shutting down!"); + emit shuttingDown(); + if (client->clientConnected) + { + logLine("Disconnecting from client!"); + client->stopAllDevices(); + logLine("Devices stopped."); + client->disconnect(); + logLine("Client disconnected."); + } + + stream->flush(); + file->close(); + +} + +void Gui::closeEvent(QCloseEvent* event) +{ +// event->ignore(); + saveSettings(); + shutdown(); + QApplication::quit(); + event->accept(); +} + +void Gui::updateStatus() +{ + QString statusHTML = ""; + QString connectionStatus = "

"; + QString hintText = ""; + if (client->clientConnected) + { + connectionStatus += "Connected to Intiface!"; + if (client->getDevices().size() == 0) + { + connectionStatus += " No devices available."; + hintText += "Put your device in pairing mode, then press 'Devices / Start Scanning' in Intiface Central."; + } + } + else + { + connectionStatus += "Can't connect to Intiface!"; + hintText += "Make sure Intiface Central is installed and running.
If Intiface is on another device, enter the address (ws://...) on the 'Config' tab."; + } + connectionStatus += "

"; + + statusHTML += connectionStatus; + if (!hintText.isEmpty()) + { + statusHTML += "

" + hintText + "

"; + } + + if (!rulesStatusText.isEmpty()) + { + statusHTML += "

" + rulesStatusText + "

"; + } + + statusHTML += ""; + ui->statusLabel->setText(statusHTML); +} + +void Gui::update() +{ + QMutexLocker lk(&telemetryMutex); + + long time = latestTelemetry.timestamp; + if (time == 0) + { + time = QDateTime::currentMSecsSinceEpoch() * 1000; + } + + + long deltaUS = lastTime > 0 ? time - lastTime : 0; + double delta = deltaUS / (1000.0 * 1000.0); + + double deltaDistance = latestTelemetry.speed * delta; + distanceCounter += deltaDistance; + + lastTime = time; + + TelemetryState tele; + if (!paused) + tele = latestTelemetry; + + tele.distance = distanceCounter; + + double power = 0.0; + + for (RuleModule* module: modules) + { + power = module->process(tele, power, delta); + } + + power = std::clamp(power, 0.0, 1.0); + + if (client->clientConnected && !paused) + { + + std::vector devices = client->getDevices(); + for (DeviceClass device : devices) + { + logLine(QString("Power to %2: %1").arg(power).arg(QString::fromStdString(device.deviceName))); + client->sendScalar(device, power); + } + } + + updateStatus(); + + ui->distanceLabel->setText(QString("%1").arg(tele.distance / 1000.0, 0, 'f', 3)); + ui->outputGraph->addPoint(power); +} + +void Gui::setPause(bool pause) +{ + paused = pause; + if (paused) + logLine("Game paused."); + else + logLine("Game unpaused."); +} + +void Gui::logLine(QString line) +{ + qDebug() << line; + // *stream << line << "\n"; + // stream->flush(); +} + +void Gui::receiveTelemetry(TelemetryState tele) +{ + if (!paused) + { + // logLine(QString("speed %1 rpm %2").arg(tele.speed).arg(tele.rpm)); + QMutexLocker lk(&telemetryMutex); + + latestTelemetry = tele; + + ui->speedLabel->setText(QString("%1").arg(tele.speed * 3.6, 4, 'f', 1)); + ui->rpmLabel->setText(QString("%1").arg(tele.rpm, 0, 'f', 0)); + } +} + +void Gui::rulesStatusUpdate(QString status) +{ + rulesStatusText = status; + QTimer::singleShot(1000, [this](){ rulesStatusText = ""; }); +} + +void Gui::gameplayEvent(QString event, QMap attributes) +{ + qDebug() << "Game event: " << event << ", atts: " << attributes; + for (RuleModule* module: modules) + { + module->gameEvent(event, attributes); + } +} + +void Gui::callbackFunction(const mhl::Messages msg) +{ + if (msg.messageType == mhl::MessageTypes::DeviceList) + { + logLine("Device List callback"); + } + if (msg.messageType == mhl::MessageTypes::DeviceAdded) + { + logLine("Device Added callback"); + } + if (msg.messageType == mhl::MessageTypes::ServerInfo) + { + logLine("Server Info callback"); + } + if (msg.messageType == mhl::MessageTypes::DeviceRemoved) + { + logLine("Device Removed callback"); + } + if (msg.messageType == mhl::MessageTypes::SensorReading) + { + logLine("Sensor Reading callback"); + } +} + +void Gui::on_jobCompleteTestButton_pressed() +{ + QMap atts; + atts["earned.xp"] = QVariant::fromValue(125); + atts["revenue"] = QVariant::fromValue(2041); + gameplayEvent("job.delivered", atts); +} + diff --git a/src/gui/gui.h b/src/gui/gui.h new file mode 100644 index 0000000..1b102a7 --- /dev/null +++ b/src/gui/gui.h @@ -0,0 +1,90 @@ +#ifndef GUI_H +#define GUI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../rulemodule.h" +#include "telemetry.h" + +#include "../libraries/buttplugCpp/include/buttplugclient.h" + +const QString CANT_CONNECT_MESSAGE = "

Can't connect to Intiface server!

Make sure that Intiface Central is installed and running.

[ If Intiface is on a different device, enter the address (ws://...) on the 'Config' tab. ]

"; + +namespace Ui { +class Gui; +} + +class Gui : public QMainWindow +{ + Q_OBJECT + +static const int UPDATE_INTERVAL_MS = 100; + +public: + explicit Gui(QWidget *parent = nullptr); + ~Gui(); + + void loadSettings(); + void saveSettings(); + void shutdown(); + void logLine(QString line); + + void receiveTelemetry(TelemetryState tele); + void gameplayEvent(QString event, QMap attributes); + void setPause(bool pause); + + virtual void closeEvent(QCloseEvent* event) override; + +protected slots: + void update(); + void updateStatus(); + void rulesStatusUpdate(QString status); + +signals: + void shuttingDown(); + +private slots: + void on_jobCompleteTestButton_pressed(); + +private: + Ui::Gui* ui; + + Client* client; + QFile* file; + QTextStream* stream; + QTimer* timer; + + QMutex telemetryMutex; + TelemetryState latestTelemetry; + double distanceCounter = 0.0; + bool paused = true; + + scs_timestamp_t lastTime = 0; + + QList modules; + SpeedModule speedMod; + RPMModule rpmMod; + CrimeModule crimeMod; + JobModule jobMod; + DistanceModule distMod; + + QString rulesStatusText; + + void callbackFunction(const mhl::Messages msg); + std::function callbackFunctionPtr = [this](const mhl::Messages msg){callbackFunction(msg);}; + +// static void doubleConnect(QObject* objectA, QObject* objectB, const QMetaMethod aSignal, const QMetaMethod aSlot, const QMetaMethod bSignal, const QMetaMethod bSlot ); + + + +}; + +#endif // GUI_H diff --git a/src/gui/gui.ui b/src/gui/gui.ui new file mode 100644 index 0000000..53780c3 --- /dev/null +++ b/src/gui/gui.ui @@ -0,0 +1,556 @@ + + + Gui + + + + 0 + 0 + 460 + 498 + + + + Euro Fuck Simulator + + + + + + + + 0 + 96 + + + + + + + + + + Speed (KPH) + + + Qt::AlignCenter + + + + + + + RPM + + + Qt::AlignCenter + + + + + + + Distance (KM) + + + Qt::AlignCenter + + + + + + + + Monospace + 14 + true + + + + QFrame::Panel + + + QFrame::Raised + + + 69.0 + + + Qt::AlignCenter + + + + + + + + Monospace + 14 + true + + + + QFrame::Panel + + + QFrame::Raised + + + 420 + + + Qt::AlignCenter + + + + + + + + Monospace + 14 + true + + + + QFrame::Panel + + + QFrame::Raised + + + 3621 + + + Qt::AlignCenter + + + + + + + + + 1 + + + + Status + + + + + + <html><head/><body><p><span style=" font-style:italic;">(Starting up, just a moment...)</span></p></body></html> + + + Qt::AlignCenter + + + true + + + true + + + + + + + + Rules + + + + 6 + + + + + true + + + + + 0 + -276 + 404 + 525 + + + + + + + + true + + + + Speed to Power + + + + + + + <html><head/><body><p>Maps the truck's speed to output power.</p><p>Has an optional &quot;speed bump&quot; feature that wiggles up and down as you travel along.</p></body></html> + + + Enabled + + + + + + + Top Speed + + + + + + + <html><head/><body><p>The speed (in KPH) needed for maximum output power.</p><p>The highest speed limit on any road is 90KPH.</p><p>Most trucks struggle to get over 80KPH with a trailer attached.</p></body></html> + + + + + + + Speed Bump Frequency + + + + + + + <html><head/><body><p>The frequency of the &quot;speed bump&quot; wave at maximum speed, in Hz (cycles per second).</p><p>The waves get slower as you slow down, with the effect that the bumps happen at the same distance down the road.</p></body></html> + + + + + + + Speed Bump Amplitude + + + + + + + <html><head/><body><p>The amplitude (size) of the speed bump waves. 1.0 is 100%.</p></body></html> + + + + + + + Output Power + + + + + + + <html><head/><body><p>The total output power when travelling at top speed. 1.0 is 100%.</p><p>Use this to mix the output with other modules, or limit the power so other things are more noticable!</p></body></html> + + + + + + + + true + + + + RPM to Power + + + + + + + <html><head/><body><p>Maps the engine RPM to output power. Feel the vroom!</p></body></html> + + + Enabled + + + + + + + Top RPM + + + + + + + <html><head/><body><p>The engine RPM needed for maximum output.</p><p>Most truck engines cap out at 2000RPM, and change gears at 500RPM.</p></body></html> + + + + + + + Bottom RPM + + + + + + + <html><head/><body><p>The engine RPM for zero output. Use this to make nothing happen when the engine is idling or changing gear.</p><p><br/></p><p>Most truck engines idle at 500RPM.</p><p><br/></p></body></html> + + + + + + + Output Power + + + + + + + <html><head/><body><p>The total output power when at top RPM. 1.0 is 100%.</p><p>Use this to mix the output with other modules, or limit the power so other things are more noticable!</p></body></html> + + + + + + + + true + + + + Punish for Crimes + + + + + + + <html><head/><body><p>Punishes you for comitting crimes by slashing the output power for a period of time.</p><p>The duration depends on how much you got fined.</p></body></html> + + + Enabled + + + + + + + Seconds per €100 + + + + + + + <html><head/><body><p>How long you get punished (in seconds) for each €100 fined.</p><p>The fine for most offenses is between €100 and €800, although some can go as high as €5000!</p><p>With a value of 30 that's between 30 seconds and 4 minutes.</p></body></html> + + + + + + + Power Multiplier + + + + + + + <html><head/><body><p>How much the output gets multiplied by while the punishment is active; For example, 0.1 means that only 10% makes it through. Use 0.0 for no output at all.</p><p>Note: if the value is above 1.0 you're actually getting <span style=" font-style:italic;">rewarded</span> for breaking the law...</p></body></html> + + + + + + + + true + + + + Reward for Completion + + + + + + + <html><head/><body><p>Rewards you for finishing a job by pushing the output power up for a period of time, depending on how much money and XP you earned.</p><p>The output will ramp up to a peak very quickly, then fade away over time.</p></body></html> + + + Enabled + + + + + + + Seconds per €100 + + + + + + + <html><head/><body><p>How many seconds the reward lasts for each €100 earned.</p><p>Jobs can earn from €500 to over €10,000; with a value of 2, the range will be 10 seconds to 3 minutes!</p><p>This stacks from the reward from the XP earned (below).</p></body></html> + + + + + + + Seconds per 100XP + + + + + + + <html><head/><body><p>How long the reward lasts for each 100XP earned.</p><p>Jobs can earn between 200XP and 1000XP depending on your performance.</p></body></html> + + + + + + + Power Boost + + + + + + + + + + + true + + + + Distance Rewards + + + + + + + Enabled + + + + + + + Average Distance + + + + + + + + + + Randomness + + + + + + + + + + Duration + + + + + + + + + + Power Boost + + + + + + + + + + + + + + + Config + + + + + + + + Intiface Address + + + + + + + ws://127.0.0.1:12345 + + + ws://127.0.0.1:12345 + + + + + + + Job Complete + + + + + + + + + + + + + + + + FancyGraph + QWidget +
gui/fancygraph.h
+ 1 +
+ + FancySlider + QWidget +
gui/fancyslider.h
+ 1 +
+
+ + +
diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..37a0245 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,47 @@ + +#include + +#include "gui/gui.h" +#include "qapphost.h" +#include "QTemporaryDir" + +QAppHost* host; +Gui* gui; + +void boot() +{ + if (gui == nullptr) + { + gui = new Gui(); + TelemetryState tele; + tele.timestamp = 0; + tele.speed = 9.72222222222; + tele.rpm = 1000; + tele.gear = 7; + gui->setPause(false); + gui->receiveTelemetry(tele); + gui->setVisible(true); + } +} + +int main(int argc, char** argv) +{ + + try + { + if (host == nullptr) + { + host = new QAppHost(&boot); +// std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + host->waitForThread(); + } + catch (std::exception ex) + { + std::cerr << "Killed by exception: " << ex.what() << std::endl; + return SCS_RESULT_generic_error; + } + + std::cout << "Execution finished." << std::endl; +} diff --git a/src/qapphost.cpp b/src/qapphost.cpp new file mode 100644 index 0000000..5825de0 --- /dev/null +++ b/src/qapphost.cpp @@ -0,0 +1,100 @@ +#include "qapphost.h" + +#include +#include +#include +#include +#ifdef WITH_QT_JSON_SETTINGS +#include +#endif + +int argc = 1; +char* argv[] = {"SharedLibrary", nullptr}; +char** nargv = nullptr; + +QAppHost::QAppHost(std::function callback) +{ + callbackFunction = callback; + std::cout << "QAppHost::QAppHost()" << std::endl; + if (thread == nullptr) + { + thread = new std::thread(&QAppHost::threadRun, this); + std::cout << "Host thread created." << std::endl; + } +} + +//QAppHost::QAppHost(int& nargc, char** args) +//{ +// argc = nargc; +// nargv = args; +// std::cout << "QAppHost::QAppHost()" << std::endl; +// if (thread == nullptr) +// { +// thread = new std::thread(&QAppHost::threadRun, this); +// std::cout << "Host thread created." << std::endl; +// } +//} + +//QAppHost::~QAppHost() +//{ +// qDebug() << "QAppHost::~QAppHost()"; +// if (app != nullptr) +// { +// delete app; +// qDebug() << "App deleted."; +// } +// if (thread != nullptr) +// { +// delete thread; +// qDebug() << "Thread deleted."; +// } +//} + +void QAppHost::waitForThread() +{ + if (thread != nullptr && thread->joinable()) + thread->join(); +} + +void QAppHost::threadRun() +{ + std::cout << "QAppHost::threadRun()" << std::endl; + + if (QApplication::instance() == nullptr) + { + app = new QApplication(argc, nargv == nullptr ? argv : nargv); + std::cout << "App created." << std::endl; + + QCoreApplication::setOrganizationName("Catboy Labs"); + QCoreApplication::setOrganizationDomain("mysoft.com"); + QCoreApplication::setApplicationName("Euro Fuck Simulator"); +#ifdef WITH_QT_JSON_SETTINGS + QtJsonSettings::Initialize(); +#endif + + QTimer::singleShot(250, callbackFunction); + std::cout << "Boot timer created." << std::endl; + + app->exec(); + qDebug() << "App finished executing."; + } + + qDebug() << "QAppHost::threadRun() finished"; +} + +void QAppHost::shutdown() +{ + qDebug() << "QAppHost::shutdown()"; + if (QApplication::instance() != nullptr) + { + QApplication::instance()->quit(); + qDebug() << "App quit."; + } +// if (thread != nullptr && thread->joinable()) +// { +// qDebug() << "Thread joining..."; +// thread->join(); +// qDebug() << "Thread joined."; +// } + qDebug() << "QAppHost::shutdown() finished"; +} diff --git a/src/qapphost.h b/src/qapphost.h new file mode 100644 index 0000000..87c1dce --- /dev/null +++ b/src/qapphost.h @@ -0,0 +1,26 @@ +#ifndef QAPPHOST_H +#define QAPPHOST_H + +#include +#include +#include + +class QAppHost +{ +private: + QApplication* app; + std::thread* thread; + std::function callbackFunction; + +public: + explicit QAppHost(std::function callback); +// QAppHost(int& argc, char** argv); + ~QAppHost() = default; + + void waitForThread(); + void threadRun(); + void shutdown(); + +}; + +#endif // QAPPHOST_H diff --git a/src/rulemodule.cpp b/src/rulemodule.cpp new file mode 100644 index 0000000..04d4e6b --- /dev/null +++ b/src/rulemodule.cpp @@ -0,0 +1,209 @@ +#include "rulemodule.h" + +#include +#include +#include +#include + +#include "math.h" +#include "common/scssdk_telemetry_common_gameplay_events.h" + +double peakedSine(double x, double peakTime = 0.2) +{ + double stretchedX; + if (x < peakTime) + stretchedX = x / (peakTime*2.0); + else + stretchedX = 0.5+( ( (x-peakTime)/(1-peakTime) )*0.5 ); + + double ret = 1.0-((1.0+(std::cos(stretchedX * M_PI * 2.0)))/2.0); + qDebug() << "peakedSine(" << x << ") =" << ret; + return ret; +} + +RuleModule::RuleModule(QObject *parent) + : QObject{parent} +{ +// const QMetaObject* meta = metaObject(); +// for (int idx = 1; idx < meta->propertyCount(); idx++) +// { +// QMetaProperty prop = meta->property(idx); +// connect(this, prop.notifySignal(), this, [&]()); +// } +} + +void RuleModule::save(QSettings& settings) const +{ + const QMetaObject* meta = metaObject(); + settings.beginGroup(objectName()); + for (int idx = 1; idx < meta->propertyCount(); idx++) + { + QMetaProperty prop = meta->property(idx); + QVariant value = prop.read(this); + qDebug() << "Saving settings: " << objectName() << "/" << prop.name() << " = " << value; + settings.setValue(prop.name(), value); + } + settings.endGroup(); +} + +void RuleModule::load(QSettings& settings) +{ + const QMetaObject* meta = metaObject(); + settings.beginGroup(objectName()); + for (int idx = 1; idx < meta->propertyCount(); idx++) + { + QMetaProperty prop = meta->property(idx); + QVariant value = settings.value(prop.name(), prop.read(this)); + qDebug() << "Loading settings: " << objectName() << "/" << prop.name() << " = " << value; + prop.write(this, value); + emit prop.notifySignal(); + } + settings.endGroup(); +} + +double SpeedModule::process(const TelemetryState& telemetry, double previous, double delta) +{ + double ret = previous; + if (Enabled && TopSpeed > 0) + { + double speedN = std::abs(telemetry.speed * 3.6) / TopSpeed; + sine_x += delta * speedN; + double sine = std::sin(sine_x * WaveFrequency * M_PI * 2); + double power = std::clamp(speedN + (sine * speedN * WaveAmplitude), 0.0, 1.0); + ret += power * MaxOutput; + } + return ret; +} + +double RPMModule::process(const TelemetryState& telemetry, double previous, double delta) +{ + double ret = previous; + if (Enabled && (TopRPM - BottomRPM) > 0) + { + double rpmN = (telemetry.rpm - BottomRPM) / (TopRPM - BottomRPM); + ret += rpmN * MaxOutput; + } + return ret; +} + +void CrimeModule::gameEvent(const QString name, const QMap attributes) +{ + if (name == SCS_TELEMETRY_GAMEPLAY_EVENT_player_fined) + { + if (attributes.contains(SCS_TELEMETRY_GAMEPLAY_EVENT_ATTRIBUTE_fine_amount)) + { + uint64_t amount = attributes[SCS_TELEMETRY_GAMEPLAY_EVENT_ATTRIBUTE_fine_amount].toULongLong(); + timeout += amount * SecondsPerEuro; + } + if (attributes.contains(SCS_TELEMETRY_GAMEPLAY_EVENT_ATTRIBUTE_fine_offence)) + { + QString offence = attributes[SCS_TELEMETRY_GAMEPLAY_EVENT_ATTRIBUTE_fine_offence].toString(); + + // You... + if (offence == "crash") + whatYouDid = "killed someone"; + else if (offence == "red_signal") + whatYouDid = "ran a red light"; + else if (offence == "avoid_sleeping") + whatYouDid = "were caught being eepy"; + else if (offence == "speeding_camera") + whatYouDid = "got flashed"; + else if (offence == "no_lights") + whatYouDid = "forgot to press the headlight button twice"; +// else if (offence == "speeding") +// whatYouDid = ""; +// else if (offence == "avoid_weighing") +// whatYouDid = ""; + else if (offence == "illegal_trailer") + whatYouDid = "got found packing heat"; + else if (offence == "avoid_inspection") + whatYouDid = "missed penis inspection day"; +// else if (offence == "illegal_border_crossing") +// whatYouDid = ""; +// else if (offence == "hard_shoulder_violation") +// whatYouDid = ""; +// else if (offence == "damaged_vehicle_usage") +// whatYouDid = ""; + else if (offence == "generic") + whatYouDid = "failed the vibe check"; + else + whatYouDid = "broke the law"; + // ...! You'll be punished for X seconds! + } + } +} + +double CrimeModule::process(const TelemetryState& telemetry, double previous, double delta) +{ + double ret = previous; + if (Enabled) + { + if (timeout > 0.0) + { + timeout -= delta; + ret *= PowerMultiplier; + + emit statusUpdate(QString("You %2! You'll be punished for %1 seconds!").arg(timeout, 0, 'f', 1).arg(whatYouDid)); + } + } + return ret; +} + +void JobModule::gameEvent(const QString name, const QMap attributes) +{ + if (name == SCS_TELEMETRY_GAMEPLAY_EVENT_job_delivered) + { + totalTime = 0.0; + if (attributes.contains(SCS_TELEMETRY_GAMEPLAY_EVENT_ATTRIBUTE_revenue)) + { + uint64_t amount = attributes[SCS_TELEMETRY_GAMEPLAY_EVENT_ATTRIBUTE_revenue].toULongLong(); + totalTime += (amount/100.0) * SecondsPerEuro; + } + if (attributes.contains(SCS_TELEMETRY_GAMEPLAY_EVENT_ATTRIBUTE_earned_xp)) + { + uint64_t amount = attributes[SCS_TELEMETRY_GAMEPLAY_EVENT_ATTRIBUTE_earned_xp].toULongLong(); + totalTime += (amount/100.0) * SecondsPerXP; + } + timeout = totalTime; + } +} + +double JobModule::process(const TelemetryState& telemetry, double previous, double delta) +{ + double ret = previous; + if (Enabled) + { + if (timeout > 0.0) + { + timeout -= delta; + ret += peakedSine((totalTime-timeout)/totalTime, 0.1) * PowerBoost; + + emit statusUpdate(QString("Well done! You'll be rewarded for %1 seconds!").arg(timeout, 0, 'f', 1)); + } + } + return ret; +} + +double DistanceModule::process(const TelemetryState& telemetry, double previous, double delta) +{ + double ret = previous; + if (Enabled) + { + if (telemetry.distance > nextReward) + { + double random = (1.0 - Randomness) + (QRandomGenerator::global()->generateDouble() * 2.0 * Randomness); + qDebug () << "Random factor: " << random; + double distanceToNext = Distance * 1000.0 * random; + timeout = Duration; + nextReward += distanceToNext; + qDebug() << "Next distance reward will be in " << distanceToNext << " metres, at " << nextReward; + } + if (timeout > 0.0) + { + timeout -= delta; + ret += peakedSine((Duration-timeout)/Duration, 0.4) * PowerBoost; + emit statusUpdate(QString("You've driven so far! You'll be rewarded for %1 seconds!").arg(timeout, 0, 'f', 1)); + } + } + return ret; +} diff --git a/src/rulemodule.h b/src/rulemodule.h new file mode 100644 index 0000000..db214e3 --- /dev/null +++ b/src/rulemodule.h @@ -0,0 +1,124 @@ +#ifndef RULEMODULE_H +#define RULEMODULE_H + +#include +#include + +#include "telemetry.h" + + + +// I'm sorry I'm just addicted to efficiency ok +#define FANCY_PROPERTY(Type,Name,Default) \ +Q_PROPERTY(Type Name READ get##Name WRITE set##Name NOTIFY Name##Changed); \ +protected: \ + Type Name = Default; \ +public: \ + Type get##Name() { return Name; } \ + Q_SIGNAL void Name##Changed(Type newValue); \ +public slots: \ + void set##Name(Type value) { bool changed = (Name != value); Name = value; if (changed) emit Name##Changed(Name); }; + +class RuleModule : public QObject +{ + Q_OBJECT; + +public: + explicit RuleModule(QObject *parent = nullptr); + + virtual double process(const TelemetryState& telemetry, double previous, double delta) = 0; + virtual void gameEvent(const QString name, const QMap attributes) {}; + + void load(QSettings& settings); + void save(QSettings& settings) const; + + FANCY_PROPERTY(bool, Enabled, false); + +signals: + void statusUpdate(QString status); + +public slots: + virtual void gameEvent(QString eventType, double param) {} + +}; + +class SpeedModule: public RuleModule +{ + Q_OBJECT; +public: + SpeedModule() { setObjectName("speed"); } + virtual double process(const TelemetryState& telemetry, double previous, double delta) override; + + FANCY_PROPERTY(double, TopSpeed, 65.0); + FANCY_PROPERTY(double, MaxOutput, 1.0); + FANCY_PROPERTY(double, WaveFrequency, 3.0); + FANCY_PROPERTY(double, WaveAmplitude, 0.25); + +private: + double sine_x = 0.0; +}; + +class RPMModule: public RuleModule +{ + Q_OBJECT; +public: + RPMModule() {setObjectName("rpm");} + virtual double process(const TelemetryState& telemetry, double previous, double delta) override; + + FANCY_PROPERTY(double, TopRPM, 2500.0); + FANCY_PROPERTY(double, BottomRPM, 500.0); + FANCY_PROPERTY(double, MaxOutput, 1.0); +}; + +class CrimeModule: public RuleModule +{ + Q_OBJECT; +public: + CrimeModule() {setObjectName("crime");} + virtual double process(const TelemetryState& telemetry, double previous, double delta) override; + virtual void gameEvent(const QString name, const QMap attributes) override; + + FANCY_PROPERTY(double, SecondsPerEuro, 30.0 / 400.0); + FANCY_PROPERTY(double, PowerMultiplier, 0.1); + +private: + double timeout = 0.0; + QString whatYouDid = ""; +}; + +class JobModule: public RuleModule +{ + Q_OBJECT; +public: + JobModule() {setObjectName("job");} + virtual double process(const TelemetryState& telemetry, double previous, double delta) override; + virtual void gameEvent(const QString name, const QMap attributes) override; + + FANCY_PROPERTY(double, SecondsPerEuro, 30.0 / 7000.0); + FANCY_PROPERTY(double, SecondsPerXP, 30.0 / 500.0); + FANCY_PROPERTY(double, PowerBoost, 0.7); + +private: + double totalTime = 0.0; + double timeout = 0.0; +}; + +class DistanceModule: public RuleModule +{ + Q_OBJECT; +public: + DistanceModule() {setObjectName("distance");} + virtual double process(const TelemetryState& telemetry, double previous, double delta) override; + + FANCY_PROPERTY(double, Distance, 10.0); + FANCY_PROPERTY(double, Randomness, 1.0); + FANCY_PROPERTY(double, Duration, 30.0); + FANCY_PROPERTY(double, PowerBoost, 0.7); + +private: + double nextReward = 0.0; + double timeout = 0.0; + +}; + +#endif // RULEMODULE_H diff --git a/src/telemeter.cpp b/src/telemeter.cpp new file mode 100644 index 0000000..400dd06 --- /dev/null +++ b/src/telemeter.cpp @@ -0,0 +1,284 @@ +// Windows stuff. +#ifdef _WIN32 +# define WINVER 0x0500 +# define _WIN32_WINNT 0x0500 +# include +#endif + +// SDK +#include "../libraries/scs-sdk/include/scssdk_telemetry.h" +#include "../libraries/scs-sdk/include/eurotrucks2/scssdk_eut2.h" +#include "../libraries/scs-sdk/include/eurotrucks2/scssdk_telemetry_eut2.h" +#include "../libraries/scs-sdk/include/amtrucks/scssdk_ats.h" +#include "../libraries/scs-sdk/include/amtrucks/scssdk_telemetry_ats.h" + +#include "telemetry.h" +#include "qapphost.h" +//#include "eurofucksimulator.h" +#include "gui/gui.h" + +#include +#include +#include + +//#include +//#include + +#define UNUSED(x) + +scs_timestamp_t last_timestamp = static_cast(-1); +TelemetryState telemetry; + +QAppHost* host = nullptr; +//EuroFuckSimulator* eu = nullptr; +Gui* gui = nullptr; + +QVariant value_to_variant(scs_value_t value) +{ + QVariant ret; + switch (value.type) + { + case SCS_VALUE_TYPE_INVALID: + { + ret = QVariant(); + break; + } + case SCS_VALUE_TYPE_bool: { + ret = QVariant::fromValue(value.value_bool.value); + break; + } + case SCS_VALUE_TYPE_s32: { + ret = QVariant::fromValue(static_cast(value.value_s32.value)); + break; + } + case SCS_VALUE_TYPE_u32: { + ret = QVariant::fromValue(static_cast(value.value_u32.value)); + break; + } + case SCS_VALUE_TYPE_s64: { + ret = QVariant::fromValue(value.value_s64.value); + break; + } + case SCS_VALUE_TYPE_u64: { + ret = QVariant::fromValue(value.value_u64.value); + break; + } + case SCS_VALUE_TYPE_float: { + ret = QVariant::fromValue(value.value_float.value); + break; + } + case SCS_VALUE_TYPE_double: { + ret = QVariant::fromValue(value.value_double.value); + break; + } + case SCS_VALUE_TYPE_fvector: { + ret = QVariant::fromValue(QVector3D(value.value_fvector.x,value.value_fvector.y,value.value_fvector.z)); + break; + } + case SCS_VALUE_TYPE_dvector: { + ret = QVariant::fromValue(QVector3D(value.value_dvector.x,value.value_dvector.y,value.value_dvector.z)); + break; + } + case SCS_VALUE_TYPE_euler: { + ret = QVariant::fromValue(QQuaternion::fromEulerAngles(value.value_euler.heading * 360.0f,value.value_euler.pitch * 360.0f,value.value_euler.roll * 360.0f)); + break; + } +// case SCS_VALUE_TYPE_fplacement: { +//// QMatrix4x4::QMatrix4x4 +//// log_line( +//// "fplacement = (%f,%f,%f) h:%f p:%f r:%f", +//// value.value_fplacement.position.x, +//// value.value_fplacement.position.y, +//// value.value_fplacement.position.z, +//// value.value_fplacement.orientation.heading * 360.0f, +//// value.value_fplacement.orientation.pitch * 360.0f, +//// value.value_fplacement.orientation.roll * 360.0f +//// ); +// break; +// } +// case SCS_VALUE_TYPE_dplacement: { +//// log_line( +//// "dplacement = (%f,%f,%f) h:%f p:%f r:%f", +//// value.value_dplacement.position.x, +//// value.value_dplacement.position.y, +//// value.value_dplacement.position.z, +//// value.value_dplacement.orientation.heading * 360.0f, +//// value.value_dplacement.orientation.pitch * 360.0f, +//// value.value_dplacement.orientation.roll * 360.0f +//// ); +// break; +// } + case SCS_VALUE_TYPE_string: { + ret = QVariant::fromValue(QString(value.value_string.value)); + break; + } + default: { + ret = QVariant(); + break; + } + } + return ret; +} + +QMap attributes_to_map(const scs_named_value_t *const attributes) +{ + QMap ret; + for (const scs_named_value_t *current = attributes; current->name; ++current) + { + QString name = QString::fromStdString(current->name); + QVariant value = value_to_variant(current->value); + ret[name] = value; + } + return ret; +} + +SCSAPI_VOID telemetry_frame_start(const scs_event_t UNUSED(event), const void *const event_info, const scs_context_t UNUSED(context)) +{ + const struct scs_telemetry_frame_start_t *const info = static_cast(event_info); + + int delta = info->paused_simulation_time - last_timestamp; + telemetry.timestamp += (delta); +// telemetry.time_delta = delta; + last_timestamp = info->paused_simulation_time; +} + +SCSAPI_VOID telemetry_frame_end(const scs_event_t UNUSED(event), const void *const UNUSED(event_info), const scs_context_t UNUSED(context)) +{ + if (gui != nullptr) + gui->receiveTelemetry(telemetry); +} + +SCSAPI_VOID telemetry_pause(const scs_event_t event, const void *const UNUSED(event_info), const scs_context_t UNUSED(context)) +{ + bool output_paused = (event == SCS_TELEMETRY_EVENT_paused); + if (gui != nullptr) + gui->setPause(output_paused); +} + +SCSAPI_VOID telemetry_configuration(const scs_event_t event, const void *const event_info, const scs_context_t UNUSED(context)) +{ + const struct scs_telemetry_configuration_t *const info = static_cast(event_info); +// qDebug() << attributes_to_map(info->attributes); +} + +SCSAPI_VOID telemetry_gameplay_event(const scs_event_t event, const void *const event_info, const scs_context_t UNUSED(context)) +{ + const struct scs_telemetry_gameplay_event_t *const info = static_cast(event_info); + + QMap map = attributes_to_map(info->attributes); + + gui->gameplayEvent(QString::fromStdString(info->id), map); +} + +SCSAPI_VOID telemetry_store_orientation(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context) +{ + assert(context); + TelemetryState *const state = static_cast(context); + + // This callback was registered with the SCS_TELEMETRY_CHANNEL_FLAG_no_value flag + // so it is called even when the value is not available. + + if (! value) { + state->orientation_available = false; + return; + } + + assert(value); + assert(value->type == SCS_VALUE_TYPE_euler); + state->orientation_available = true; + state->heading = value->value_euler.heading * 360.0f; + state->pitch = value->value_euler.pitch * 360.0f; + state->roll = value->value_euler.roll * 360.0f; +} + +SCSAPI_VOID telemetry_store_float(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context) +{ + // The SCS_TELEMETRY_CHANNEL_FLAG_no_value flag was not provided during registration + // so this callback is only called when a valid value is available. + + assert(value); + assert(value->type == SCS_VALUE_TYPE_float); + assert(context); + *static_cast(context) = value->value_float.value; +} + +SCSAPI_VOID telemetry_store_s32(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context) +{ + // The SCS_TELEMETRY_CHANNEL_FLAG_no_value flag was not provided during registration + // so this callback is only called when a valid value is available. + + assert(value); + assert(value->type == SCS_VALUE_TYPE_s32); + assert(context); + *static_cast(context) = value->value_s32.value; +} + +void boot() +{ + if (gui == nullptr) + { + gui = new Gui(); + gui->setVisible(true); + } +} + +SCSAPI_RESULT scs_telemetry_init(const scs_u32_t version, const scs_telemetry_init_params_t *const params) +{ + if (version != SCS_TELEMETRY_VERSION_1_01) { + return SCS_RESULT_unsupported; + } + + try + { + if (host == nullptr) + { + host = new QAppHost(&boot); + } + } + catch (std::exception ex) + { + std::cerr << "Killed by exception: " << ex.what() << std::endl; + return SCS_RESULT_generic_error; + } + + const scs_telemetry_init_params_v101_t *const version_params = static_cast(params); + + const bool events_registered = + (version_params->register_for_event(SCS_TELEMETRY_EVENT_frame_start, telemetry_frame_start, nullptr) == SCS_RESULT_ok) && + (version_params->register_for_event(SCS_TELEMETRY_EVENT_frame_end, telemetry_frame_end, nullptr) == SCS_RESULT_ok) && + (version_params->register_for_event(SCS_TELEMETRY_EVENT_paused, telemetry_pause, nullptr) == SCS_RESULT_ok) && + (version_params->register_for_event(SCS_TELEMETRY_EVENT_started, telemetry_pause, nullptr) == SCS_RESULT_ok); + + if (! events_registered) + { + version_params->common.log(SCS_LOG_TYPE_error, "Unable to register event callbacks"); + return SCS_RESULT_generic_error; + } + + version_params->register_for_event(SCS_TELEMETRY_EVENT_configuration, telemetry_configuration, nullptr); + version_params->register_for_event(SCS_TELEMETRY_EVENT_gameplay, telemetry_gameplay_event, nullptr); + + version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_speed, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry.speed); + version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_engine_rpm, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry.rpm); + version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_engine_gear, SCS_U32_NIL, SCS_VALUE_TYPE_s32, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_s32, &telemetry.gear); + + + + return SCS_RESULT_ok; +} + +SCSAPI_VOID scs_telemetry_shutdown(void) +{ + if (gui != nullptr) + { + gui->setVisible(false); + gui->saveSettings(); + gui->shutdown(); + delete gui; + } + if (host != nullptr) + { + host->shutdown(); + delete host; + } +} diff --git a/src/telemetry.h b/src/telemetry.h new file mode 100644 index 0000000..14f6e7a --- /dev/null +++ b/src/telemetry.h @@ -0,0 +1,31 @@ +#ifndef TELEMETRY_H +#define TELEMETRY_H + +#include "../libraries/scs-sdk/include/scssdk.h" + +struct TelemetryState +{ + scs_timestamp_t timestamp = 0; +// int time_delta = 0; +// scs_timestamp_t raw_rendering_timestamp; +// scs_timestamp_t raw_simulation_timestamp; +// scs_timestamp_t raw_paused_simulation_timestamp; + + bool orientation_available = false; + float heading = 0; + float pitch = 0; + float roll = 0; + + float speed = 0; + float rpm = 0; + int gear = 0; + double distance = 0; + +}; + +//enum GameplayEvent +//{ + +//}; + +#endif // TELEMETRY_H