Files
dusk/src/dusk/input/input.c
T
YourWishes 4d95415232 Move global item collected-state, deadzone, and story flags into save file
Adds three new pieces of save-file state, all following the same shape:
the save file is the single source of truth, not a separate live runtime
copy that gets synced in/out.

- globalitemstore.h/.c: per-global-entity-ID "collected" flags
  (savefile_t.globalItemCollected), so a global item entity's init
  callback can check whether it was already picked up in a prior session
  without needing to keep the entity itself alive to remember that.

- Gamepad deadzone: removed input_t.deadzone entirely. The setting UI and
  every platform's actual deadzone-applying code (inputGetDeadzoneDolphin/
  SDL2, previously hardcoded per-platform literals that the settings menu
  didn't actually affect) now read savefile_t.deadzone directly via
  saveGet(SAVE_ACTIVE_SLOT). Default lives in savefile.h
  (SAVE_DEADZONE_DEFAULT), stamped onto every slot in saveInit().

- Story flags: STORY_FLAG_VALUES (a live, codegen-initialized array) is
  replaced by savefile_t.storyFlags, read/written via the existing
  storyFlagGet()/storyFlagSet() call sites (now macros/functions over the
  active save file instead of a separate array). tools/story.py now
  generates STORY_FLAG_DEFAULTS (const) instead; storyFlagInitDefaults()
  stamps those onto a save the first time it's used (file->exists false),
  called from rpgInit().

Added SAVE_ACTIVE_SLOT (0) to savefile.h as the one shared "which slot is
actually being played" constant, replacing three different local/implicit
0s (uigamemenu.c, rpg.c, and now the settings/input call sites).

Verified round-trip on Linux (all three together in one save/load cycle);
both Linux and PSP build clean.
2026-08-04 11:10:44 -05:00

273 lines
7.3 KiB
C

/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "input.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
#include "util/math.h"
#include "time/time.h"
#include "event/event.h"
input_t INPUT;
errorret_t inputInit(void) {
memoryZero(&INPUT, sizeof(input_t));
for(uint8_t i = 0; i < INPUT_ACTION_COUNT; i++) {
INPUT.actions[i].action = (inputaction_t)i;
INPUT.actions[i].lastValue = 0.0f;
INPUT.actions[i].currentValue = 0.0f;
eventInit(
&INPUT.actions[i].onPressed,
INPUT.actions[i].onPressedCallbacks,
INPUT.actions[i].onPressedUsers,
INPUT_ACTION_CALLBACK_COUNT_MAX
);
eventInit(
&INPUT.actions[i].onReleased,
INPUT.actions[i].onReleasedCallbacks,
INPUT.actions[i].onReleasedUsers,
INPUT_ACTION_CALLBACK_COUNT_MAX
);
}
#ifdef inputInitPlatform
errorChain(inputInitPlatform());
#endif
errorOk();
}
void inputUpdate(void) {
#ifdef inputUpdatePlatform
inputUpdatePlatform();
#endif
// Reset all actions
inputactiondata_t *action = &INPUT.actions[0];
do {
#ifdef DUSK_TIME_DYNAMIC
action->lastDynamicValue = action->currentDynamicValue;
action->currentDynamicValue = 0.0f;
if(!TIME.dynamicUpdate) {
action->lastValue = action->currentValue;
action->currentValue = 0.0f;
}
#else
action->lastValue = action->currentValue;
action->currentValue = 0.0f;
#endif
action++;
} while(action < &INPUT.actions[INPUT_ACTION_COUNT]);
// For each button...
inputbuttondata_t *cur = &INPUT_BUTTON_DATA[0];
while(cur->name) {
cur->lastVal = cur->curVal;
cur->curVal = inputButtonGetValue(cur->button);
if(cur->curVal == 0.0f) {
cur++;
continue;
}
// Update current val.
#ifdef DUSK_TIME_DYNAMIC
INPUT.actions[cur->action].currentDynamicValue = mathMax(
cur->curVal, INPUT.actions[cur->action].currentDynamicValue
);
if(!TIME.dynamicUpdate) {
INPUT.actions[cur->action].currentValue = mathMax(
cur->curVal, INPUT.actions[cur->action].currentValue
);
}
#else
INPUT.actions[cur->action].currentValue = mathMax(
cur->curVal, INPUT.actions[cur->action].currentValue
);
#endif
cur++;
}
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) return;
#endif
for(uint8_t i = INPUT_ACTION_NULL + 1; i < INPUT_ACTION_COUNT; i++) {
inputactiondata_t *act = &INPUT.actions[i];
bool_t isDown = act->currentValue > 0.0f;
bool_t wasDown = act->lastValue > 0.0f;
if(isDown && !wasDown) eventInvoke(&act->onPressed, act);
if(!isDown && wasDown) eventInvoke(&act->onReleased, act);
}
}
float_t inputGetCurrentValue(const inputaction_t action) {
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) return inputGetCurrentValueDynamic(action);
#endif
assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds");
return INPUT.actions[action].currentValue;
}
float_t inputGetLastValue(const inputaction_t action) {
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) return inputGetLastValueDynamic(action);
#endif
assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds");
return INPUT.actions[action].lastValue;
}
#ifdef DUSK_TIME_DYNAMIC
float_t inputGetCurrentValueDynamic(const inputaction_t action) {
assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds");
return INPUT.actions[action].currentDynamicValue;
}
float_t inputGetLastValueDynamic(const inputaction_t action) {
assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds");
return INPUT.actions[action].lastDynamicValue;
}
bool_t inputIsDownDynamic(const inputaction_t action) {
return inputGetCurrentValueDynamic(action) > 0.0f;
}
bool_t inputWasDownDynamic(const inputaction_t action) {
return inputGetLastValueDynamic(action) > 0.0f;
}
bool_t inputPressedDynamic(const inputaction_t action) {
return inputIsDownDynamic(action) && !inputWasDownDynamic(action);
}
bool_t inputReleasedDynamic(const inputaction_t action) {
return !inputIsDownDynamic(action) && inputWasDownDynamic(action);
}
float_t inputAxisDynamic(
const inputaction_t neg,
const inputaction_t pos
) {
return inputGetCurrentValueDynamic(pos) -
inputGetCurrentValueDynamic(neg);
}
void inputAxis2DDynamic(
const inputaction_t negX, const inputaction_t posX,
const inputaction_t negY, const inputaction_t posY,
vec2 result
) {
assertNotNull(result, "Result vector cannot be null");
result[0] = inputAxisDynamic(negX, posX);
result[1] = inputAxisDynamic(negY, posY);
}
void inputAngle2DDynamic(
const inputaction_t negX, const inputaction_t posX,
const inputaction_t negY, const inputaction_t posY,
vec2 result
) {
assertNotNull(result, "Result vector cannot be null");
float_t x = inputAxisDynamic(negX, posX);
float_t y = inputAxisDynamic(negY, posY);
float_t mag = sqrtf(x * x + y * y);
if(mag <= 0.0f) {
result[0] = 0.0f;
result[1] = 0.0f;
return;
}
if(mag > 1.0f) {
x /= mag;
y /= mag;
}
result[0] = x;
result[1] = y;
}
#endif
bool_t inputIsDown(const inputaction_t action) {
return inputGetCurrentValue(action) > 0.0f;
}
bool_t inputWasDown(const inputaction_t action) {
return inputGetLastValue(action) > 0.0f;
}
bool_t inputPressed(const inputaction_t action) {
return inputIsDown(action) && !inputWasDown(action);
}
bool_t inputReleased(const inputaction_t action) {
return !inputIsDown(action) && inputWasDown(action);
}
float_t inputAxis(const inputaction_t neg, const inputaction_t pos) {
assertTrue(neg < INPUT_ACTION_COUNT, "Negative input action out of bounds");
assertTrue(pos < INPUT_ACTION_COUNT, "Positive input action out of bounds");
return inputGetCurrentValue(pos) - inputGetCurrentValue(neg);
}
void inputAxis2D(
const inputaction_t negX, const inputaction_t posX,
const inputaction_t negY, const inputaction_t posY,
vec2 result
) {
assertNotNull(result, "Result vector cannot be null");
result[0] = inputAxis(negX, posX);
result[1] = inputAxis(negY, posY);
}
void inputAngle2D(
const inputaction_t negX, const inputaction_t posX,
const inputaction_t negY, const inputaction_t posY,
vec2 result
) {
assertNotNull(result, "Result vector cannot be null");
float_t x = inputAxis(negX, posX);
float_t y = inputAxis(negY, posY);
float_t mag = sqrtf(x * x + y * y);
if(mag <= 0.0f) {
result[0] = 0.0f;
result[1] = 0.0f;
return;
}
if(mag > 1.0f) {
x /= mag;
y /= mag;
}
result[0] = x;
result[1] = y;
}
void inputBind(const inputbutton_t button, const inputaction_t act) {
assertTrue(
act < INPUT_ACTION_COUNT,
"Invalid input action"
);
assertTrue(act != INPUT_ACTION_NULL, "Cannot bind to NULL action");
// Get the button data for this button.
inputbuttondata_t *data = inputButtonGetData(button);
assertNotNull(data, "Input button not found");
// Bind the action.
data->action = act;
}
float_t inputDeadzone(const float_t rawValue, const float_t deadzone) {
if(rawValue < deadzone) return 0.0f;
return (rawValue - deadzone) / (1.0f - deadzone);
}