Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

custom OSContPad? #344

Open
briaguya-ai opened this issue Sep 19, 2023 · 3 comments
Open

custom OSContPad? #344

briaguya-ai opened this issue Sep 19, 2023 · 3 comments

Comments

@briaguya-ai
Copy link
Collaborator

the current LUS implementation of OSContPad includes things beyond what exists on the n64 (multiple analog sticks, gyro), but is still very opinionated as to what can exist on the pad.

i think in order to satisfy the two port dev user stories from #342 it could be helpful to add some flexibility here.

To satisfy

As a developer of a port using LUS, I should be able to get up and running without building my own controller mapping/configuration menu.

it could be helpful to provide controller functionality that utilizes an OSContPad that matches the authentic data structure. Only including n64 buttons/one analog stick. The existing LUS controller functionality could then be provided via OSContPadExtended, which would provide the default "modern controller" version of OSContPad that exists in LUS today (n64 controller + a few extra things)

To satisfy

As a developer of a port using LUS, I should be able to provide a tailored controller mapping/configuration experience for players.

we would want to provide the ability for any given port could provide their own OSContPad structure that can deviate as far from the authentic n64 controller OSContPad as the port would like. This would allow for a port to create an OSContPad structure based on in-game actions, for example, instead of having

#define BTN_CRIGHT 0x00001
#define BTN_CLEFT 0x00002
#define BTN_CDOWN 0x00004
#define BTN_CUP 0x00008

a port could have

#define ACTION_OCARINA_A4 0x00001 
#define ACTION_OCARINA_B4 0x00002 
#define ACTION_OCARINA_F4 0x00004 
#define ACTION_OCARINA_D4 0x00008 
#define ACTION_ITEM_RIGHT 0x00010 
#define ACTION_ITEM_LEFT 0x00020 
#define ACTION_ITEM_DOWN 0x00040  

this would be very port specific, as each port would know which actions they would want to be able to map (as opposed to relying on original n64 controller mapping to game actions), so LUS wouldn't need to provide any examples or UI for this, just the framework to do so

@Kenix3
Copy link
Owner

Kenix3 commented Sep 21, 2023

I'm not fundamentally against the idea of an action oriented system instead of mapping to N64 buttons. I have some issues with it that would need to be worked out to make a change like this.

The first is that I'm not totally sure I see the conceptual value for the kinds of projects LUS is meant to support, outside of one. What I mean by that is everyone knows these are N64 games and are used to an emulator-like approach to bind controls. In other words, people are already used to mapping physical to virtual N64 buttons. There is one project which this assumption is a lot weaker on. Plus we are not an emulator. Really looking to discuss this point more than anything.

The actual problem is that an action system is a fundamental change to how the game interacts with LUS. It no longer calls a libultra function and gets back libultra data. You now need to query the state of an action instead of a ContPad. I think this needs more baking time before it's ready. If you're not suggesting to remove the old system, but instead treat an action as a bit flag for which virtual buttons are pressed, we are getting closer, but it still needs thought on how it would interact where the buttons are the bits. I'm other words, how would a port out of the box that doesn't want to deal with this complexity query data? The systems need to support both. How does that new port transition?

This would also mean we no longer need to change the controller headers, which would be a good thing. I don't think there's very many other places, maybe 2 or 3, where we modify headers. I'd be nice to take a look and see if we could theoretically reset them and allow games to provide their own headers.

@briaguya-ai
Copy link
Collaborator Author

everyone knows these are N64 games and are used to an emulator-like approach to bind controls.

however, we already rely heavily on workarounds in SoH to provide context specific control customization. a specific example from SoH is playing the ocarina.

from a player perspective: a player must map their physical controller in one menu, then edit settings in another menu to add custom ocarina playback controls (which, due to the nature of n64 controller based mapping, is based on n64 buttons)

from an implementation perspective: 10 cvars are needed to provide dpad, right stick, and custom ocarina controls
in func_800ED458 we have this:

bool customControls = CVarGetInteger("gCustomOcarinaControls", 0);
bool dpad = CVarGetInteger("gDpadOcarina", 0);
bool rStick = CVarGetInteger("gRStickOcarina", 0);

and then this:

Audio_OcaUpdateBtnMap(customControls, dpad, rStick);

which calls into this:

// Function originally not called, so repurposing for control mapping
void Audio_OcaUpdateBtnMap(bool customControls, bool dpad, bool rStick) {
    if (customControls) {
        sOcarinaD5BtnMap = CVarGetInteger("gOcarinaD5BtnMap", BTN_CUP);
        sOcarinaB4BtnMap = CVarGetInteger("gOcarinaB4BtnMap", BTN_CLEFT);
        sOcarinaA4BtnMap = CVarGetInteger("gOcarinaA4BtnMap", BTN_CRIGHT);
        sOcarinaF4BtnMap = CVarGetInteger("gOcarinaF4BtnMap", BTN_CDOWN);
        sOcarinaD4BtnMap = CVarGetInteger("gOcarinaD4BtnMap", BTN_A);
    } else {
        sOcarinaD5BtnMap = BTN_CUP;
        sOcarinaB4BtnMap = BTN_CLEFT;
        sOcarinaA4BtnMap = BTN_CRIGHT;
        sOcarinaF4BtnMap = BTN_CDOWN;
        sOcarinaD4BtnMap = BTN_A;
    }

    if (dpad) {
        sOcarinaD5BtnMap |= BTN_DUP;
        sOcarinaB4BtnMap |= BTN_DLEFT;
        sOcarinaA4BtnMap |= BTN_DRIGHT;
        sOcarinaF4BtnMap |= BTN_DDOWN;
    }

    if (rStick) {
        sOcarinaD5BtnMap |= RSTICK_UP;
        sOcarinaB4BtnMap |= RSTICK_LEFT;
        sOcarinaA4BtnMap |= RSTICK_RIGHT;
        sOcarinaF4BtnMap |= RSTICK_DOWN;
    }

    sOcarinaAllowedBtnMask = (
        sOcarinaD5BtnMap |
        sOcarinaB4BtnMap |
        sOcarinaA4BtnMap |
        sOcarinaF4BtnMap |
        sOcarinaD4BtnMap
    );
}

as well as extra logic to handle the right stick here:

s8 rstick_x = input->cur.right_stick_x;
s8 rstick_y = input->cur.right_stick_y;
const s8 sensitivity = 64;
if (rstick_x > sensitivity) {
    sCurOcarinaBtnPress |= RSTICK_RIGHT;
}
if (rstick_x < -sensitivity) {
    sCurOcarinaBtnPress |= RSTICK_LEFT;
}
if (rstick_y > sensitivity) {
    sCurOcarinaBtnPress |= RSTICK_UP;
}
if (rstick_y < -sensitivity) {
    sCurOcarinaBtnPress |= RSTICK_DOWN;
}

and then more modification is done here:

u32 noteSharpBtnMap;
if (customControls) {
    noteSharpBtnMap = CVarGetInteger("gOcarinaSharpBtnMap", BTN_R);
} else {
    noteSharpBtnMap = BTN_R;
}

and here:

u32 noteFlatBtnMap;
if (customControls) {
    noteFlatBtnMap = CVarGetInteger("gOcarinaFlatBtnMap", BTN_Z);
} else {
    noteFlatBtnMap = BTN_Z;
}

with an action-based system this could be simplified immensely and done with 0 cvars. Audio_OcaUpdateBtnMap would no longer need to be called. instead, all we would need to do is set the static map vars here:

s32 sOcarinaD5BtnMap = BTN_CUP;
s32 sOcarinaB4BtnMap = BTN_CLEFT;
s32 sOcarinaA4BtnMap = BTN_CRIGHT;
s32 sOcarinaF4BtnMap = BTN_CDOWN;
s32 sOcarinaD4BtnMap = BTN_A;

to be the actions instead, for example

s32 sOcarinaD5BtnMap = ACTION_OCARINA_D5;
s32 sOcarinaB4BtnMap = ACTION_OCARINA_B4;
s32 sOcarinaA4BtnMap = ACTION_OCARINA_A4;
s32 sOcarinaF4BtnMap = ACTION_OCARINA_F4;
s32 sOcarinaD4BtnMap = ACTION_OCARINA_D4;

It no longer calls a libultra function and gets back libultra data.

i'd argue it already doesn't (we get back extended libultra data), but i understand your point. if we want to continue to support

As a developer of a port using LUS, I should be able to get up and running without building my own controller mapping/configuration menu.

then providing the default OSContPad and/or the proposed OSContPadExtended would very much still be required

If you're not suggesting to remove the old system, but instead treat an action as a bit flag for which virtual buttons are pressed, we are getting closer, but it still needs thought on how it would interact where the buttons are the bits.

This is where a port would need to do some heavy reworking. I wouldn't expect young ports to go this direction as it requires finding all the places where the authentic bit structure of button in OSContPad is expected, and replacing it with a heavily modified version (not sure about name, but let's use OSActionPad as an example)

the Input struct in the port would be modified to use the action pad

typedef struct {
    /* 0x00 */ OSActionPad cur;
    /* 0x?? */ OSActionPad prev;
    /* 0x?? */ OSActionPad press; // X/Y store delta from last frame
    /* 0x?? */ OSActionPad rel; // X/Y store adjusted
} Input; // size = 0x??

and the port-specific OSActionPad could be built however makes the most sense for the port. it could be as simple changing button to be a uint64_t instead of a uint16_t and having 64 actions live within button, or it could be more complex - for example grouping related actions into their own variables (think menuButton and gameplayButton, where the menu would compare bits against menuButton and in game stuff could compare bits against gameplayButton)

I think this needs more baking time before it's ready.

I agree, and I think it probably makes sense to build as much of this as possible out within a port instead of LUS to start, and then see what can be done to move away from an implementation that I assume will basically be "we aren't using LUS for control stuff right now"

tentative plan would be to:

  • create a new SoH branch to do discovery/proof of concept work for this
  • copy the libultraship/src/controller directory into soh/Enhancements/controller.
  • Utilize the work from feat: allow creating uninitialized context #340 to initialize an LUS context without the LUS control deck.
  • Get SoH working with with the code from soh/Enhancements/controller instead of the LUS control deck stuff
  • check in to see what needed to happen for that to work (did LUS need changes? if so why/are they reasonable? i figure this would be a good point to step back and think/talk a little.
  • hack away at the code in soh/Enhancements/controller, try to get some basic action based stuff working
  • start to settle in to patterns that feel like they make sense
  • use discovered sensible patterns to fully implement action-based mapping in SoH
  • see what needed to change in LUS (ideally nothing)
  • create a new branch of LUS, copy over the soh/Enhancements/controller directory, make a draft PR (with no intention of it getting merged)
  • discuss the changes, see if there are things that LUS could do that would make it so this would be possible without a port just straight up copying a directory from LUS
  • PR any changes that make sense (think adding functionality to LUS that can be utilized by a port implementing their own control stuff)
  • rework the SoH branch to utilize the new upstream LUS functionality
  • PR a pretty good v1 for action based mapping to SoH

@briaguya-ai briaguya-ai changed the title OSContPadExtended/custom OSContPad? custom OSContPad? Apr 19, 2024
@Kenix3
Copy link
Owner

Kenix3 commented Apr 19, 2024

The initial suggestion will no longer be an issue once libultra headers are gone. #406

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants