commit
ddfafb89b7
@ -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><html><head/><body><p><span style=" font-style:italic;">(Starting up, just a moment...)</span></p></body></html></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><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></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><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></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><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></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><html><head/><body><p>The amplitude (size) of the speed bump waves. 1.0 is 100%.</p></body></html></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><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></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><html><head/><body><p>Maps the engine RPM to output power. Feel the vroom!</p></body></html></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><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></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><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></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><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></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><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></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><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></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><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></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><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></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><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></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><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></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…
Reference in new issue