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

[RFC] Generalized staves #3

Open
CT075 opened this issue Jan 23, 2021 · 0 comments
Open

[RFC] Generalized staves #3

CT075 opened this issue Jan 23, 2021 · 0 comments

Comments

@CT075
Copy link

CT075 commented Jan 23, 2021

Currently, staff behavior is pretty hardcoded in all aspects. For example, we have Window_Target_Staff.target_mode, which manually checks whether the staff is_attack_staff, Torch, Heals, etc, and the resulting enum is then dispatched on again to get the valid target list. As it stands, this is only going to get worse -- at least from what I can see, it doesn't look like Fortify is easily expressible with the existing staff API, so we'd have to add another variant there, which will just get longer and more difficult to maintain as time goes on.

So let's try to brainstorm a design. Here's my proposal:

Disclaimer: I'm pretty unfamiliar with the codebase, so I will almost certainly use incorrect function/class names and/or have some serious misunderstandings, but hopefully what I'm saying will make enough sense to get some feedback

Staves, currently

If you ask me, the behavior of a given staff has three components:

  • Range
  • Target selection
  • Action performed on the (possibly empty list of) target(s).

At the moment, each of these are dealt with a relatively decoupled, ad-hoc way -- at best, we can specify the Staff_traits field in Data_Weapon, but these are handled somewhere completely differently (ie, in Window_Target_Staff). Furthermore, these don't compose -- we cannot, without great difficulty, make a staff that (for example) casts Barrier on a selected target and also heals everyone in a Mag/2 range (at least, if I'm understanding the code correctly).

Basic design proposal

Consider the action flow of, say, Warp:

  1. Find all targets in range (adjacent allies).
  2. Select a single target, unit A.
  3. Find all targets in range (all unoccupied tiles in Mag/2 range).
  4. Select target, coordinates x,y.
  5. Perform action (move A to x,y).

Note that, with the current way staves are handled, I don't even think this is possible without hacking a bunch of extra stuff on (mostly to account for the extra target selection phase).

We might enable this workflow like this (using placeholder descriptive names for things):

  • Every staff has a possibly-repeating "select targets" phase -- this might be represented by a function fetch_targets of type Fn<(Map_state, Unit, Staff_state), (List<(int, int)>, bool)>
    • The input is the current state of the map, the user, and some kind of aggregator that stores the list of targets so far (we could just have the type Staff_state be literally List<(int, int)>).
    • The output is a pair (L ,k) where L is the (possibly empty!) list of targetable squares (targeting a particular unit would just be selecting the square that the unit is on), and k returns whether there are more targets to be selected. Note that this opens up a ton of expressiveness -- we could have a staff that warps a unit a distance limited by the target's stats, instead of just the user's!
  • After calling fetch_targets, we can augment the Staff_state variable with the selected target, and potentially repeat until we run out of targets.
  • Then, the staff effect is a function action of type Fn<(Map_state, Staff_state), Map_state>, which augments the state of the map (fog vision, unit position, etc) given a list of targets.

With just these primitives, we can express nearly every kind of staff (note: a real implementation would almost certainly be more involved in every way -- these are just placeholder names/operations that are intended to be evocative of the intended behavior):

  • Regular healing staves return (adjacent_squares_with_allies(user), false), representing that we select from the squares adjacent to the user containing an ally, and false to say that we don't have to select any more targets after selecting.
  • Torch would return (range(user.loc, user.mag / 2), false), signifying that we can center it on any square in range and that we don't need to select a new target afterwards.
  • If we instead wanted a variant on Torch that was centered on the user, its fetch_targets function returns (user.loc, false), representing that we only have one valid target (the square we're standing on).
  • Warp returns (adjacent_squares_with_allies(user), true) if its Staff_state has no previously-selected targets (so we select the unit to be warped), and (traversable_squares_in_range(user, targets[0].occupant, user.mag/2), false) if it does (choose the square to warp the previously-selected target).
  • We might implement Rewarp similarly to the above.

I think the action function is relatively self-explanatory, so I won't go into much further detail about that.

Extensions/Details

  • For things like range display, or staves with no target, we might augment the return type of fetch_targets to be (List<(int, int)>, List<(int, int)>, bool, bool) (probably wrapped into some real class/struct). In order,
    • List of squares available to target
    • List of squares to highlight green
    • Ask user for target?
    • Are there more targets after this one?
  • I'm not entirely sure how actions are handled, but ideally we could also have the action function decide when to play what animation.
  • Performance-wise, we could probably do something smarter than passing closures around, but I feel like it isn't the end of the world to hold onto two function pointers (the vast majority of which will probably be static anyway?)

How will this interact with the editor?

Of course, allowing arbitrary functions doesn't play all that nicely with GUI editors. I can think of two solutions:

  • Expose some kind of plugin API where users can write their own staff scripts (dangerous, probably pretty hard?)
  • Expose only a safe set of primitives from which staff behavior can be built
    • We might have a dialogue box that lets you specify a radius around a unit and select from allies, enemies, none, alongside a "select another?" checkbox
    • For actions, we could probably expose a similar scripting language to events, where we have the list of targets and then a fixed set of operations that act on these targets
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

1 participant