#include "munga.h" #pragma hdrstop #include "inputscript.h" #include #include #include //########################################################################## // RP412INPUTSCRIPT - see the header for what and why. This file is the // how: a timeline of rows parsed once, held in a fixed array, evaluated // by walking to the last row at or before the asked-for time. //########################################################################## namespace { enum { inputScriptMaxRows = 256 }; struct InputScriptRow { float t; float throttle; float stickX; float stickY; float pedals; }; InputScriptRow gRows[inputScriptMaxRows]; int gRowCount = 0; int gLoaded = -1; // -1 not tried, 0 no script, 1 loaded Logical gArmed = False; Time gOrigin; float ClampInto(float value, float low, float high) { if (value < low) return low; if (value > high) return high; return value; } void Load() { gLoaded = 0; const char *path = getenv("RP412INPUTSCRIPT"); if (path == NULL || *path == '\0') { return; } FILE *file = fopen(path, "rt"); if (file == NULL) { DEBUG_STREAM << "InputScript: cannot read '" << path << "' - driving unscripted\n" << std::flush; return; } char line[256]; float last_t = -1.0f; while (fgets(line, sizeof(line), file) != NULL && gRowCount < inputScriptMaxRows) { InputScriptRow row; if (sscanf(line, " %f %f %f %f %f", &row.t, &row.throttle, &row.stickX, &row.stickY, &row.pedals) != 5) { continue; // comments, blanks, ragged lines } // // Clamped HERE, not at sample time, so a script asking for // throttle 2.0 is corrected once and visibly rather than // silently every step - and the mapper's own Verify range // checks can never trip on scripted input. // row.throttle = ClampInto(row.throttle, 0.0f, 1.0f); row.stickX = ClampInto(row.stickX, -1.0f, 1.0f); row.stickY = ClampInto(row.stickY, -1.0f, 1.0f); row.pedals = ClampInto(row.pedals, -1.0f, 1.0f); if (row.t < last_t) { DEBUG_STREAM << "InputScript: row at t=" << row.t << " is out of order - dropped\n" << std::flush; continue; } last_t = row.t; gRows[gRowCount++] = row; } fclose(file); if (gRowCount > 0) { gLoaded = 1; DEBUG_STREAM << "InputScript: '" << path << "', " << gRowCount << " row(s), last at t=" << gRows[gRowCount - 1].t << "s\n" << std::flush; } else { DEBUG_STREAM << "InputScript: '" << path << "' held no usable rows - driving unscripted\n" << std::flush; } } } int RPInputScript_Active() { if (gLoaded < 0) { Load(); } return (gLoaded == 1) ? 1 : 0; } void RPInputScript_Arm(const Time &origin) { if (!RPInputScript_Active()) { return; } gOrigin = origin; gArmed = True; DEBUG_STREAM << "InputScript: armed at the green light\n" << std::flush; } int RPInputScript_Sample( const Time &now, float *throttle_out, float *stick_x_out, float *stick_y_out, float *pedals_out ) { if (!gArmed || gLoaded != 1) { return 0; } Scalar t = now - gOrigin; if (t < (Scalar) 0) { t = (Scalar) 0; } // // The last row at or before t holds; before the first row, neutral. // A linear walk, but the list is tiny and already ordered. // const InputScriptRow *current = NULL; for (int i = 0; i < gRowCount; ++i) { if (gRows[i].t <= (float) t) { current = &gRows[i]; } else { break; } } if (current == NULL) { *throttle_out = 0.0f; *stick_x_out = 0.0f; *stick_y_out = 0.0f; *pedals_out = 0.0f; } else { *throttle_out = current->throttle; *stick_x_out = current->stickX; *stick_y_out = current->stickY; *pedals_out = current->pedals; } return 1; }