You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
Find all targets in range (adjacent allies).
Select a single target, unit A.
Find all targets in range (all unoccupied tiles in Mag/2 range).
Select target, coordinates x,y.
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
The text was updated successfully, but these errors were encountered:
Currently, staff behavior is pretty hardcoded in all aspects. For example, we have
Window_Target_Staff.target_mode
, which manually checks whether the staffis_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 likeFortify
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:
At the moment, each of these are dealt with a relatively decoupled, ad-hoc way -- at best, we can specify the
Staff_traits
field inData_Weapon
, but these are handled somewhere completely differently (ie, inWindow_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:
A
.x,y
.A
tox,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):
fetch_targets
of typeFn<(Map_state, Unit, Staff_state), (List<(int, int)>, bool)>
Staff_state
be literallyList<(int, int)>
).(L ,k)
whereL
is the (possibly empty!) list of targetable squares (targeting a particular unit would just be selecting the square that the unit is on), andk
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!fetch_targets
, we can augment theStaff_state
variable with the selected target, and potentially repeat until we run out of targets.action
of typeFn<(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):
(adjacent_squares_with_allies(user), false)
, representing that we select from the squares adjacent to the user containing an ally, andfalse
to say that we don't have to select any more targets after selecting.(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.fetch_targets
function returns(user.loc, false)
, representing that we only have one valid target (the square we're standing on).(adjacent_squares_with_allies(user), true)
if itsStaff_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).I think the
action
function is relatively self-explanatory, so I won't go into much further detail about that.Extensions/Details
fetch_targets
to be(List<(int, int)>, List<(int, int)>, bool, bool)
(probably wrapped into some real class/struct). In order,action
function decide when to play what animation.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:
allies, enemies, none
, alongside a "select another?" checkboxThe text was updated successfully, but these errors were encountered: