A mod for Euro Truck Simulator 2 to support "haptic feedback devices" for "reasons"
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

322 lines
8.2 KiB

7 months ago
/**
* @brief Simple input device
*
* Generates device with a few axes and buttons which change with time
*/
// Windows stuff.
#ifdef _WIN32
# define WINVER 0x0500
# define _WIN32_WINNT 0x0500
# include <windows.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
// SDK
#include "scssdk_input.h"
#include "eurotrucks2/scssdk_eut2.h"
#include "eurotrucks2/scssdk_input_eut2.h"
#include "amtrucks/scssdk_ats.h"
#include "amtrucks/scssdk_input_ats.h"
#define UNUSED(x)
/**
* @brief Logging support.
*/
FILE *log_file = NULL;
const unsigned AXIS_COUNT = 2;
const unsigned BUTTON_COUNT = 3;
// Simulation of the values.
struct device_data_t
{
// Current state of the dummy inputs.
float axes[AXIS_COUNT];
unsigned button_state;
// Last time when we simulated movement.
time_t last_movement_time;
// Index of next input we will report.
unsigned next_reported_input;
};
device_data_t dummy_device;
// Management of the log file.
bool init_log(void)
{
if (log_file) {
return true;
}
log_file = fopen("input.log", "wt");
if (! log_file) {
return false;
}
fprintf(log_file, "Log opened\n");
return true;
}
void finish_log(void)
{
if (! log_file) {
return;
}
fprintf(log_file, "Log ended\n");
fclose(log_file);
log_file = NULL;
}
void log_print(const char *const text, ...)
{
if (! log_file) {
return;
}
va_list args;
va_start(args, text);
vfprintf(log_file, text, args);
va_end(args);
}
void log_line(const char *const text, ...)
{
if (! log_file) {
return;
}
va_list args;
va_start(args, text);
vfprintf(log_file, text, args);
fprintf(log_file, "\n");
va_end(args);
}
void simulate_device(device_data_t &device)
{
// Move once per second.
const time_t now = time(NULL);
if (device.last_movement_time == now) {
return;
}
device.last_movement_time = now;
// Move each axis to maximum before moving the next
// one reseting everything to zero if all of them
// reach maximum.
if (device.axes[AXIS_COUNT - 1] >= 1.0f) {
memset(device.axes, 0, sizeof(device.axes));
}
for (unsigned i = 0; i < AXIS_COUNT; ++i) {
if (device.axes[i] < 1.0f) {
device.axes[i] += 0.125f;
break;
}
}
device.button_state = (device.button_state + 1) % (BUTTON_COUNT * 2);
}
SCSAPI_VOID input_active_callback(const scs_u8_t active, const scs_context_t UNUSED(context))
{
// This callback is optional. A real implementation could do some cleanup
// on deactivation or can leave it to event callback with
// SCS_INPUT_EVENT_CALLBACK_FLAG_first_after_activation flag.
if (active > 0) {
log_line("Device activated");
}
else {
log_line("Device deactivated");
}
}
SCSAPI_RESULT input_event_callback(scs_input_event_t *const event_info, const scs_u32_t flags, const scs_context_t context)
{
device_data_t &device = *static_cast<device_data_t *>(context);
if (flags & SCS_INPUT_EVENT_CALLBACK_FLAG_first_after_activation) {
log_line("First call after activation");
}
// Simulates retrieval of current data from some external device at start
// of the frame.
if (flags & SCS_INPUT_EVENT_CALLBACK_FLAG_first_in_frame) {
simulate_device(device);
device.next_reported_input = 0;
}
// Did we process all events for this frame?
if (device.next_reported_input >= (AXIS_COUNT + BUTTON_COUNT)) {
return SCS_RESULT_not_found;
}
// Generate events for individual inputs. If the number of inputs is small,
// it is fine to generate events even if there was no change.
event_info->input_index = device.next_reported_input;
if (device.next_reported_input < AXIS_COUNT) {
event_info->value_float.value = device.axes[device.next_reported_input];
}
else {
event_info->value_bool.value = (((device.next_reported_input - AXIS_COUNT) * 2) == device.button_state) ? 1 : 0;
}
++device.next_reported_input;
return SCS_RESULT_ok;
}
/**
* @brief Input API initialization function.
*
* See scssdk_input.h
*/
SCSAPI_RESULT scs_input_init(const scs_u32_t version, const scs_input_init_params_t *const params)
{
// We currently support only one version.
if (version != SCS_INPUT_VERSION_1_00) {
return SCS_RESULT_unsupported;
}
const scs_input_init_params_v100_t *const version_params = static_cast<const scs_input_init_params_v100_t *>(params);
if (! init_log()) {
version_params->common.log(SCS_LOG_TYPE_error, "Unable to initialize the log file");
return SCS_RESULT_generic_error;
}
// Check application version. Note that this example uses fairly basic features which are likely to be supported
// by any future SCS trucking game however more advanced applications might want to at least warn the user if there
// is game or version they do not support.
log_line("Game '%s' %u.%u", version_params->common.game_id, SCS_GET_MAJOR_VERSION(version_params->common.game_version), SCS_GET_MINOR_VERSION(version_params->common.game_version));
if (strcmp(version_params->common.game_id, SCS_GAME_ID_EUT2) == 0) {
// Below the minimum version there might be some missing features (only minor change) or
// incompatible values (major change).
const scs_u32_t MINIMAL_VERSION = SCS_INPUT_EUT2_GAME_VERSION_1_00;
if (version_params->common.game_version < MINIMAL_VERSION) {
log_line("WARNING: Too old version of the game, some features might behave incorrectly");
}
// Future versions are fine as long the major version is not changed.
const scs_u32_t IMPLEMENTED_VERSION = SCS_INPUT_EUT2_GAME_VERSION_CURRENT;
if (SCS_GET_MAJOR_VERSION(version_params->common.game_version) > SCS_GET_MAJOR_VERSION(IMPLEMENTED_VERSION)) {
log_line("WARNING: Too new major version of the game, some features might behave incorrectly");
}
}
else if (strcmp(version_params->common.game_id, SCS_GAME_ID_ATS) == 0) {
// Below the minimum version there might be some missing features (only minor change) or
// incompatible values (major change).
const scs_u32_t MINIMAL_VERSION = SCS_INPUT_ATS_GAME_VERSION_1_00;
if (version_params->common.game_version < MINIMAL_VERSION) {
log_line("WARNING: Too old version of the game, some features might behave incorrectly");
}
// Future versions are fine as long the major version is not changed.
const scs_u32_t IMPLEMENTED_VERSION = SCS_INPUT_ATS_GAME_VERSION_CURRENT;
if (SCS_GET_MAJOR_VERSION(version_params->common.game_version) > SCS_GET_MAJOR_VERSION(IMPLEMENTED_VERSION)) {
log_line("WARNING: Too new major version of the game, some features might behave incorrectly");
}
}
else {
log_line("WARNING: Unsupported game, some features or values might behave incorrectly");
}
// Setup the device information.
const scs_input_device_input_t inputs[] = {
{"a1", "Analog axis 1", SCS_VALUE_TYPE_float},
{"a2", "Analog axis 2", SCS_VALUE_TYPE_float},
{"b1", "Button 1", SCS_VALUE_TYPE_bool},
{"b2", "Button 2", SCS_VALUE_TYPE_bool},
{"b3", "Button 3", SCS_VALUE_TYPE_bool},
};
assert(AXIS_COUNT == 2);
assert(BUTTON_COUNT == 3);
scs_input_device_t device_info;
memset(&device_info, 0, sizeof(device_info));
device_info.name = "example";
device_info.display_name = "Example SDK Device";
device_info.type = SCS_INPUT_DEVICE_TYPE_generic;
device_info.input_count = sizeof(inputs) / sizeof(inputs[0]);
device_info.inputs = inputs;
device_info.input_active_callback = input_active_callback;
device_info.input_event_callback = input_event_callback;
device_info.callback_context = &dummy_device;
memset(&dummy_device, 0, sizeof(dummy_device));
dummy_device.last_movement_time = time(NULL);
if (version_params->register_device(&device_info) != SCS_RESULT_ok) {
// Registrations created by unsuccessfull initialization are
// cleared automatically so we can simply exit.
version_params->common.log(SCS_LOG_TYPE_error, "Unable to register device");
return SCS_RESULT_generic_error;
}
return SCS_RESULT_ok;
}
/**
* @brief Input API deinitialization function.
*
* See scssdk_input.h
*/
SCSAPI_VOID scs_input_shutdown(void)
{
// Any cleanup needed. The registrations will be removed automatically.
finish_log();
}
// Cleanup
#ifdef _WIN32
BOOL APIENTRY DllMain(
HMODULE module,
DWORD reason_for_call,
LPVOID reseved
)
{
if (reason_for_call == DLL_PROCESS_DETACH) {
finish_log();
}
return TRUE;
}
#endif
#ifdef __linux__
void __attribute__ ((destructor)) unload(void)
{
finish_log();
}
#endif