Initial commit.

master
Nekojimi 7 months ago
commit ddfafb89b7
  1. 76
      .gitignore
  2. 136
      CMakeLists.txt
  3. 3
      EuroFuckSimulator_en_GB.ts
  4. 12
      src/EuroFuckSimulator_global.h
  5. 16
      src/eurofucksimulator.cpp
  6. 53
      src/eurofucksimulator.h
  7. 79
      src/gui/fancygraph.cpp
  8. 34
      src/gui/fancygraph.h
  9. 56
      src/gui/fancyslider.cpp
  10. 44
      src/gui/fancyslider.h
  11. 348
      src/gui/gui.cpp
  12. 90
      src/gui/gui.h
  13. 556
      src/gui/gui.ui
  14. 47
      src/main.cpp
  15. 100
      src/qapphost.cpp
  16. 26
      src/qapphost.h
  17. 209
      src/rulemodule.cpp
  18. 124
      src/rulemodule.h
  19. 284
      src/telemeter.cpp
  20. 31
      src/telemetry.h

76
.gitignore vendored

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

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

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_GB"></TS>

@ -0,0 +1,12 @@
#ifndef EUROFUCKSIMULATOR_GLOBAL_H
#define EUROFUCKSIMULATOR_GLOBAL_H
#include <QtCore/qglobal.h>
#if defined(EUROFUCKSIMULATOR_LIBRARY)
# define EUROFUCKSIMULATOR_EXPORT Q_DECL_EXPORT
#else
# define EUROFUCKSIMULATOR_EXPORT Q_DECL_IMPORT
#endif
#endif // EUROFUCKSIMULATOR_GLOBAL_H

@ -0,0 +1,16 @@
#include "eurofucksimulator.h"
#include <iostream>
#include <QDebug>
#include <QApplication>
#define _USE_MATH_DEFINES
#include <math.h>
EuroFuckSimulator::EuroFuckSimulator()
:QObject()
{
}

@ -0,0 +1,53 @@
#ifndef EUROFUCKSIMULATOR_H
#define EUROFUCKSIMULATOR_H
#include "EuroFuckSimulator_global.h"
#include <functional>
#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

@ -0,0 +1,79 @@
#include "fancygraph.h"
#include "qevent.h"
#include <QTimer>
#include <QPainter>
#include <QPainterPath>
#include <QDateTime>
#include <cmath>
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);
}
}

@ -0,0 +1,34 @@
#ifndef FANCYGRAPH_H
#define FANCYGRAPH_H
#include <QObject>
#include <QWidget>
#include <QTimer>
#include <QPainter>
#include <QPointF>
class FancyGraph : public QWidget
{
Q_OBJECT
private:
QTimer* timer;
QList<QPointF> 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

@ -0,0 +1,56 @@
#include "fancyslider.h"
#include "qboxlayout.h"
#include <cmath>
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<double>::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())));
}

@ -0,0 +1,44 @@
#ifndef FANCYSLIDER_H
#define FANCYSLIDER_H
#include <QWidget>
#include <QSlider>
#include <QSpinBox>
#include <QLabel>
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

@ -0,0 +1,348 @@
#include "gui.h"
#include "qdebug.h"
#include "ui_gui.h"
#include "gui/fancyslider.h"
#include <QDateTime>
#include <QScreen>
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 = "<html><head/><body>";
QString connectionStatus = "<p>";
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 <a href=\"https://intiface.com/central/\">Intiface Central</a> is installed and running.<br/>If Intiface is on another device, enter the address (ws://...) on the 'Config' tab.";
}
connectionStatus += "</p>";
statusHTML += connectionStatus;
if (!hintText.isEmpty())
{
statusHTML += "<p><em>" + hintText + "</em></p>";
}
if (!rulesStatusText.isEmpty())
{
statusHTML += "<p>" + rulesStatusText + "</p>";
}
statusHTML += "</body></html>";
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<DeviceClass> 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<QString, QVariant> 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<QString, QVariant> atts;
atts["earned.xp"] = QVariant::fromValue(125);
atts["revenue"] = QVariant::fromValue(2041);
gameplayEvent("job.delivered", atts);
}

@ -0,0 +1,90 @@
#ifndef GUI_H
#define GUI_H
#include <QMainWindow>
#include <QFile>
#include <QTextStream>
#include <QTimer>
#include <QMutex>
#include <QThread>
#include <QCloseEvent>
#include <QList>
#include <QMetaMethod>
#include "../rulemodule.h"
#include "telemetry.h"
#include "../libraries/buttplugCpp/include/buttplugclient.h"
const QString CANT_CONNECT_MESSAGE = "<html><head/><body><p>Can't connect to Intiface server!</p><p>Make sure that <a href=\"https://intiface.com/central/\"><span style=\" text-decoration: underline; color:#8ab4f8;\">Intiface Central</span></a> is installed and running.</p><p><span style=\" font-style:italic;\">[ If Intiface is on a different device, enter the address (ws://...) on the 'Config' tab. ]</span></p></body></html>";
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<QString, QVariant> 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<RuleModule*> modules;
SpeedModule speedMod;
RPMModule rpmMod;
CrimeModule crimeMod;
JobModule jobMod;
DistanceModule distMod;
QString rulesStatusText;
void callbackFunction(const mhl::Messages msg);
std::function<void(const mhl::Messages)> 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

@ -0,0 +1,556 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Gui</class>
<widget class="QMainWindow" name="Gui">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>460</width>
<height>498</height>
</rect>
</property>
<property name="windowTitle">
<string>Euro Fuck Simulator</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="FancyGraph" name="outputGraph" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>96</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Speed (KPH)</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_5">
<property name="text">
<string>RPM</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Distance (KM)</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="speedLabel">
<property name="font">
<font>
<family>Monospace</family>
<pointsize>14</pointsize>
<bold>true</bold>
</font>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<property name="text">
<string>69.0</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="rpmLabel">
<property name="font">
<font>
<family>Monospace</family>
<pointsize>14</pointsize>
<bold>true</bold>
</font>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<property name="text">
<string>420</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="distanceLabel">
<property name="font">
<font>
<family>Monospace</family>
<pointsize>14</pointsize>
<bold>true</bold>
</font>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<property name="text">
<string>3621</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Status</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="statusLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;(Starting up, just a moment...)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_3">
<attribute name="title">
<string>Rules</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="spacing">
<number>6</number>
</property>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>-276</y>
<width>404</width>
<height>525</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Speed to Power</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="speedEnabledBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Maps the truck's speed to output power.&lt;/p&gt;&lt;p&gt;Has an optional &amp;quot;speed bump&amp;quot; feature that wiggles up and down as you travel along.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Top Speed</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="FancySlider" name="speedTopSlider" native="true">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The speed (in KPH) needed for maximum output power.&lt;/p&gt;&lt;p&gt;The highest speed limit on any road is 90KPH.&lt;/p&gt;&lt;p&gt;Most trucks struggle to get over 80KPH with a trailer attached.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Speed Bump Frequency</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="FancySlider" name="speedBumpFreqSlider" native="true">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The frequency of the &amp;quot;speed bump&amp;quot; wave at maximum speed, in Hz (cycles per second).&lt;/p&gt;&lt;p&gt;The waves get slower as you slow down, with the effect that the bumps happen at the same distance down the road.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Speed Bump Amplitude</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="FancySlider" name="speedBumpAmpSlider" native="true">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The amplitude (size) of the speed bump waves. 1.0 is 100%.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Output Power</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="FancySlider" name="speedOutputPowerSlider" native="true">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The total output power when travelling at top speed. 1.0 is 100%.&lt;/p&gt;&lt;p&gt;Use this to mix the output with other modules, or limit the power so other things are more noticable!&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_10">
<property name="font">
<font>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>RPM to Power</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="rpmEnabledBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Maps the engine RPM to output power. Feel the vroom!&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Top RPM</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="FancySlider" name="rpmTopSlider" native="true">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The engine RPM needed for maximum output.&lt;/p&gt;&lt;p&gt;Most truck engines cap out at 2000RPM, and change gears at 500RPM.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Bottom RPM</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="FancySlider" name="rpmBottomSlider" native="true">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The engine RPM for zero output. Use this to make nothing happen when the engine is idling or changing gear.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Most truck engines idle at 500RPM.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Output Power</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="FancySlider" name="rpmOutputPowerSlider" native="true">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The total output power when at top RPM. 1.0 is 100%.&lt;/p&gt;&lt;p&gt;Use this to mix the output with other modules, or limit the power so other things are more noticable!&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_14">
<property name="font">
<font>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Punish for Crimes</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QCheckBox" name="crimeEnableBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Punishes you for comitting crimes by slashing the output power for a period of time.&lt;/p&gt;&lt;p&gt;The duration depends on how much you got fined.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Seconds per €100</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="FancySlider" name="crimeRateSlider" native="true">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;How long you get punished (in seconds) for each €100 fined.&lt;/p&gt;&lt;p&gt;The fine for most offenses is between €100 and €800, although some can go as high as €5000!&lt;/p&gt;&lt;p&gt;With a value of 30 that's between 30 seconds and 4 minutes.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Power Multiplier</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="FancySlider" name="crimePowerMultiplier" native="true">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;Note: if the value is above 1.0 you're actually getting &lt;span style=&quot; font-style:italic;&quot;&gt;rewarded&lt;/span&gt; for breaking the law...&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_18">
<property name="font">
<font>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Reward for Completion</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QCheckBox" name="jobRewardEnableBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;The output will ramp up to a peak very quickly, then fade away over time.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Seconds per €100</string>
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="FancySlider" name="jobRewardMoneyRateSlider" native="true">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;How many seconds the reward lasts for each €100 earned.&lt;/p&gt;&lt;p&gt;Jobs can earn from €500 to over €10,000; with a value of 2, the range will be 10 seconds to 3 minutes!&lt;/p&gt;&lt;p&gt;This stacks from the reward from the XP earned (below).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Seconds per 100XP</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="FancySlider" name="jobRewardXPRateSlider" native="true">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;How long the reward lasts for each 100XP earned.&lt;/p&gt;&lt;p&gt;Jobs can earn between 200XP and 1000XP depending on your performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>Power Boost</string>
</property>
</widget>
</item>
<item row="15" column="1">
<widget class="FancySlider" name="jobRewardPowerBoostSlider" native="true"/>
</item>
<item row="16" column="0">
<widget class="QLabel" name="label">
<property name="font">
<font>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Distance Rewards</string>
</property>
</widget>
</item>
<item row="16" column="1">
<widget class="QCheckBox" name="distanceRewardEnabledBox">
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
<item row="17" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Average Distance</string>
</property>
</widget>
</item>
<item row="17" column="1">
<widget class="FancySlider" name="distanceRewardDistSlider" native="true"/>
</item>
<item row="18" column="0">
<widget class="QLabel" name="label_23">
<property name="text">
<string>Randomness</string>
</property>
</widget>
</item>
<item row="18" column="1">
<widget class="FancySlider" name="distanceRewardRandSlider" native="true"/>
</item>
<item row="19" column="0">
<widget class="QLabel" name="label_26">
<property name="text">
<string>Duration</string>
</property>
</widget>
</item>
<item row="19" column="1">
<widget class="FancySlider" name="distanceRewardDurationSlider" native="true"/>
</item>
<item row="20" column="0">
<widget class="QLabel" name="label_27">
<property name="text">
<string>Power Boost</string>
</property>
</widget>
</item>
<item row="20" column="1">
<widget class="FancySlider" name="distanceRewardPowerBoostSlider" native="true"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Config</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Intiface Address</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit">
<property name="text">
<string>ws://127.0.0.1:12345</string>
</property>
<property name="placeholderText">
<string>ws://127.0.0.1:12345</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="jobCompleteTestButton">
<property name="text">
<string>Job Complete</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>FancyGraph</class>
<extends>QWidget</extends>
<header>gui/fancygraph.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>FancySlider</class>
<extends>QWidget</extends>
<header>gui/fancyslider.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

@ -0,0 +1,47 @@
#include <exception>
#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;
}

@ -0,0 +1,100 @@
#include "qapphost.h"
#include <QDebug>
#include <iostream>
#include <QTimer>
#include <thread>
#ifdef WITH_QT_JSON_SETTINGS
#include <qt_json_settings.h>
#endif
int argc = 1;
char* argv[] = {"SharedLibrary", nullptr};
char** nargv = nullptr;
QAppHost::QAppHost(std::function<void(void)> 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";
}

@ -0,0 +1,26 @@
#ifndef QAPPHOST_H
#define QAPPHOST_H
#include <QApplication>
#include <thread>
#include <functional>
class QAppHost
{
private:
QApplication* app;
std::thread* thread;
std::function<void(void)> callbackFunction;
public:
explicit QAppHost(std::function<void(void)> callback);
// QAppHost(int& argc, char** argv);
~QAppHost() = default;
void waitForThread();
void threadRun();
void shutdown();
};
#endif // QAPPHOST_H

@ -0,0 +1,209 @@
#include "rulemodule.h"
#include <QMetaObject>
#include <QMetaProperty>
#include <QDebug>
#include <QRandomGenerator>
#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<QString, QVariant> 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<QString, QVariant> 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;
}

@ -0,0 +1,124 @@
#ifndef RULEMODULE_H
#define RULEMODULE_H
#include <QObject>
#include <QSettings>
#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<QString, QVariant> 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<QString, QVariant> 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<QString, QVariant> 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

@ -0,0 +1,284 @@
// Windows stuff.
#ifdef _WIN32
# define WINVER 0x0500
# define _WIN32_WINNT 0x0500
# include <windows.h>
#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 <QVector3D>
#include <QMatrix4x4>
#include <QQuaternion>
//#include <QApplication>
//#include <pthread.h>
#define UNUSED(x)
scs_timestamp_t last_timestamp = static_cast<scs_timestamp_t>(-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<int>(value.value_s32.value));
break;
}
case SCS_VALUE_TYPE_u32: {
ret = QVariant::fromValue(static_cast<unsigned>(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<QString, QVariant> attributes_to_map(const scs_named_value_t *const attributes)
{
QMap<QString, QVariant> 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<const scs_telemetry_frame_start_t *>(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<const scs_telemetry_configuration_t *>(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<const scs_telemetry_gameplay_event_t *>(event_info);
QMap<QString,QVariant> 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<TelemetryState *>(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<float *>(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<int *>(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<const scs_telemetry_init_params_v101_t *>(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;
}
}

@ -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
Loading…
Cancel
Save