Initial commit.
This commit is contained in:
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