Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 39 additions & 14 deletions framework/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ the engine counts the flinch and applies the damage, then enters the move's carr

## Own moves from a manifest

A port's own moves sit in a registry by slot (`mhfu.em_move`): `mhfu.addr.OWN_MOVES` (128) of
them, and the turn keys (`EM_KEYS`, 2944), travel samples (`EM_SAMPLES`, 1280) and lunges
(`EM_LUNGES`, 12) they share, in one 24 KB partition block. These are consts in
A port's own moves sit in a registry by slot (`mhfu.em_move`): `mhfu.addr.OWN_MOVES` (160) of
them, and the turn keys (`EM_KEYS`, 2944), travel samples (`EM_SAMPLES`, 1280), code-turn values
(`EM_TURNS`, 1024) and lunges (`EM_LUNGES`, 12) they share, in one partition block. These are consts in
`addresses.toml`: raising one is that one line, every offset after it is laid out from it. A run
a pool holds already, anywhere in it, word for word (a lunge byte for byte), is shared, not
appended: `mhfu_port.moves.Pools` counts the same way, so a module its check passes fits. The seam's
Expand All @@ -161,21 +161,46 @@ move's `start`: cumulative, x toward its left and z forward in its frame when th
16-bit samples of 1/`STEER_TRAVEL_SCALE` units (8 hex digits a sample in the moves module,
`mhfu.steering`), at most `STEER_SAMPLES` a move. Each AI frame the move player adds the curve's
change since the clip cursor's last frame (straight lines between samples, so any cursor rate
and the blend's hold are right) to POSITION, turned by that YAW and scaled by the monster's size.
and the blend's hold are right) to POSITION, turned by the entity's own YAW when the move began
(the base the clips' facing rides on, as the donor's travel is in the start yaw) and scaled by the
monster's size.
It does so before the engine's root motion, wall push-out and floor in the same frame: walls
undo it as they undo root motion, and the floor keeps the height. `steer.lunge = { window = {
lo, hi }, near, span, base, range }` travels toward the hunter across the clip frames (lo, hi]: by
the hunter's XZ distance `d` when the move began, nothing under `near`, else `base + range *
min((d - near) / span, 1)` units along the bearing to the hunter then, in world axes and not
scaled. A fixed turn with a `window` turns `total` across it by the cursor instead of over
`frames` AI frames. All of it is data in the registry and in `mhfu.move_play`'s steer; a move
without it writes what it always did.
lo, hi }, near, span, base, range }` travels toward the target across the clip frames (lo, hi]: by
the target's XZ distance `d` when the move began, nothing under `near`, else `base + range *
min((d - near) / span, 1)` units along the bearing to the target then, in world axes, with `near`,
`span`, `base` and `range` times the monster's size. The target is what `ENTITY.TARGET` points at,
the player when it is 0 or not RAM: the turn reads it every frame, the lunge at the move's first,
the brain's distance and side gates too. A turn's `rate` is YAW units a frame at clip speed 1 (times
the clip's speed / 2 in play) and turns the entity's own YAW, which the clip's turn rides on. An
`after` on the same carrier plays in the AI frame its move ends, the carrier not entered again; it
keeps the entity's own YAW, and the jump in the clips' facing fades over its blend. A fixed turn
with a `window` turns `total` across it by the cursor instead of over `frames` AI frames. All of it
is data in the registry and in `mhfu.move_play`'s steer; a move without it writes what it always
did.

A move's own yaw can follow a code turn, MHP3rd's entity-yaw curves. `steer.code_turn` is the YAW
units turned since the move's `start`, cumulative, every `STEER_KEY_STEP` clip frames (4 hex digits
a value, signed 16 bits, at most `STEER_TURNS` a move; `mhfu.steering`). Each AI frame the entity's
own YAW (the base the clips' facing rides on) moves by the curve's change since the cursor's last
frame, as travel does, a loop included. `turn = "aim"` scales it by the angle to the target on the
move's first playing frame over the curve's total, so the move turns exactly that angle along the
curve's shape. A move's `aim_out` (AI frames) makes a hand-over in place carry what is still to
turn to the target at the switch into the successor's own YAW, linearly over its first `aim_out`
AI frames, on top of its own steering.

An AI script plays own move k with `enter 16 k 0` (`mhfu.addr.OWN_MOVE_MAIN`): the enter-action
stub enters the slot's carrier (the hub `(0,2)` for an empty or out-of-range slot, the game never
sees main 16), files a forced play, and the brain starts the move at the next AI step; the script
carries on after its `enter` once the move and its `after` chain end and the carrier hands back.
`mhfu.em_status()` has `own_enters` and `own_last`, and the ring shows `(16, k)`.
stub enters the slot's carrier (the hub `(0,2)` for an empty slot, the game never sees main 16),
files a forced play, and the brain starts the move at the next AI step; the script carries on after
its `enter` once the move and its `after` chain end and the carrier hands back. `mhfu.em_status()`
has `own_enters` and `own_last`, and the ring shows `(16, k)`.

An id k from `OWN_MOVES` to 255 is a combo, registered with `mhfu.em_combo(k, first, then)` once
the moves are: the stub enters the hub and files the id (it reads no slot past the registry), and
the brain plays slot `first`, which must ride the hub, with slot `then` following in place in the
AI frame it ends, as an `after` does and instead of `first`'s own. Only that play follows: any
other play of `first`, a reaction, a stop or a replacement drops the `then`. It is how one shared
move hands over to a different attack for each caller without a slot per pair.

## Rules on monster events

Expand Down
69 changes: 49 additions & 20 deletions framework/include/mhfu/em_vhook.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* request a pair entered on the game thread in the next AI frame, through
* MHFU_ENTER_ACTION;
* rules a 30 Hz brain in C: "in pair P (or own move M) for >= N frames, or on a
* monster event or a Lua signal, player distance in [lo, hi), receding or
* monster event or a Lua signal, the target's distance in [lo, hi), receding or
* closing, every condition holding -> enter (main, sub, mode) or play own move
* M', and change counters", with cooldown and budget; a rule on the flinch plays
* its move in place of the host's reaction;
Expand Down Expand Up @@ -81,7 +81,7 @@ typedef struct {
* 0: from the pair, which waits while any move plays */
uint8_t play_move; /* own move slot + 1 played instead of entering (to_main, to_sub) */
uint32_t min_frames; /* the pair (or own move) must have stood this long */
float dist_lo, dist_hi; /* player XZ distance window [lo, hi) */
float dist_lo, dist_hi; /* the target's XZ distance window [lo, hi) (mhfu_move_target_pos) */
uint32_t cooldown; /* frames between two fires */
uint32_t count; /* fires allowed; MHFU_EM_UNLIMITED for a standing rule */
uint8_t on; /* MHFU_MONSTER_*: fires in the AI frame the event is seen, from_* and
Expand All @@ -97,7 +97,7 @@ typedef struct {
typedef struct {
uint32_t installed;
uint32_t ai_ticks, act_enters, last_pair, frames;
float dist; /* player XZ distance at the last measurement */
float dist; /* the target's XZ distance at the last measurement */
uint32_t sub_hits, sub_landed, sub_last_in;
uint32_t brain_fires;
uint32_t req_pending, req_done, req_result;
Expand All @@ -116,7 +116,7 @@ typedef struct {
uint32_t gate_refused; /* tail cuts the gate refused */
uint32_t natural_rage; /* mhfu_em_natural_rage */
uint32_t own_enters; /* AI script enters of MHFU_OWN_MOVE_MAIN, whether the slot held a move or not */
uint32_t own_last; /* slot + 1 of the last that played, 0 none */
uint32_t own_last; /* slot or combo id + 1 of the last filed (a byte), 0 none */
} mhfu_em_status_t;

/* 1 while a big monster's vtable is wrapped (from its spawn to the next quest_beginning). */
Expand Down Expand Up @@ -171,28 +171,40 @@ int mhfu_em_tip(const uint8_t (*pairs)[2], int n);
* A port's own moves, by slot, in partition memory. The brain, in C on the wrapped AI step, plays
* one through the move player that same AI frame: one asked for (mhfu_em_play), else the AFTER of
* one of ours that ended on its clip, its length, its stop or a wall (forced as that one was),
* else a rule's. A slot with an AFTER ends into its carrier (its back pair) with the AI script's wake
* cleared (MOVE.HOLD_SCRIPT), so the host brain does not cut in before the next. A slot's FORCE is
* the call's, never the slot's.
* else a rule's. An AFTER on the same carrier plays in the AI frame its move ends, before the host
* step, the carrier not entered again (mhfu_em_chain). Otherwise a slot with an AFTER ends into its
* carrier (its back pair) with the AI script's wake cleared (MOVE.HOLD_SCRIPT), so the host brain
* does not cut in before the next. A slot's FORCE is the call's, never the slot's.
*
* An AI script plays a slot too: `enter MHFU_OWN_MOVE_MAIN k 0`. The slot-32 stub enters the slot's
* carrier (em75's hub (0,2) for an empty or out-of-range slot) in place of the main the game
* has no table for, and files the same forced request mhfu_em_play does; the brain plays the move
* at the next AI step. The script's next pair waits for the carrier's hand-back, as after a native
* pair: the wake the hub raises inside the run is cleared by the script's tick after it, and the
* move player clears the one its own entry of the hub raises. mhfu_em_status_t.own_enters counts
* these enters, own_last gives the slot of the last that played. */
* carrier (em75's hub (0,2) for an empty slot) in place of the main the game has no table for, and
* files the same forced request mhfu_em_play does; the brain plays the move at the next AI step.
* An id from MHFU_EM_MOVES up names a combo (mhfu_em_combo): the stub enters the hub and files the
* id, never reading a slot past the registry, and the brain plays the combo's first move; its THEN
* follows that move's chain in place. The script's next pair waits for the carrier's hand-back, as
* after a native pair: the wake the hub raises inside the run is cleared by the script's tick after
* it, and the move player clears the one its own entry of the hub raises.
* mhfu_em_status_t.own_enters counts these enters, own_last gives the slot or id + 1 (a byte) of
* the last that was filed. */
#define MHFU_EM_MOVES MHFU_OWN_MOVES
#define MHFU_EM_NO_MOVE 0xFFu
#define MHFU_EM_NO_LUNGE 0xFFu /* mhfu_em_own_t.lunge_at: no lunge */
/* The pools' sizes, MHFU_EM_KEYS, _SAMPLES and _LUNGES, are consts in addresses.toml. */
/* The pools' sizes, MHFU_EM_KEYS, _SAMPLES, _TURNS and _LUNGES, are consts in addresses.toml. */

/* struct EM_COMBO in addresses.toml: an enter id from MHFU_EM_MOVES up is a combo, the move
* `first` played on em75's hub and then `then` in place; 0xFF: none registered */
typedef struct {
uint8_t first, then;
} mhfu_em_combo_t;
#define MHFU_EM_COMBO_IDS MHFU_EM_MOVES_COMBOS_COUNT /* the ids of a byte: combos are from MHFU_EM_MOVES */

/* struct EM_OWN in addresses.toml */
typedef struct {
mhfu_move_t move;
mhfu_steer_t steer;
uint16_t key_at, key_count; /* its turn keys in mhfu_em_moves_t.keys */
uint16_t sample_at, sample_count; /* its travel samples in mhfu_em_moves_t.samples */
uint16_t turn_at, turn_count; /* its code turn in mhfu_em_moves_t.turns */
uint8_t stuck_main, stuck_sub, stuck_mode;
uint8_t after; /* slot played when this one ends; MHFU_EM_NO_MOVE */
uint8_t lunge_at; /* its lunge in mhfu_em_moves_t.lunges, or NO_LUNGE */
Expand All @@ -206,26 +218,43 @@ typedef struct {
uint32_t tag_slot, tag_started; /* the slot of the move player's move, if ours */
uint32_t plays, chained;
uint32_t key_top, sample_top, lunge_top; /* pool entries in use */
uint32_t turn_top;
uint32_t tag_then; /* the slot after the combo's first move, else NO_MOVE */
uint32_t req_force; /* REQ_SLOT starts while the notice runs */
mhfu_em_own_t moves[MHFU_EM_MOVES];
mhfu_em_combo_t combos[MHFU_EM_COMBO_IDS]; /* by enter id: only MHFU_EM_MOVES.. are read */
uint16_t keys[MHFU_EM_KEYS];
mhfu_travel_sample_t samples[MHFU_EM_SAMPLES];
mhfu_steer_lunge_t lunges[MHFU_EM_LUNGES];
int16_t turns[MHFU_EM_TURNS];
mhfu_steer_spec_t scratch; /* the steering handed to the move player */
} mhfu_em_moves_t;

/* Empties every slot and the pools: turn keys, travel samples, lunges. */
/* Empties every slot and the pools: turn keys, travel samples, lunges, code turns. */
void mhfu_em_moves_clear(void);
/* Slot `slot` plays mv, steered by s; `after` is a slot or MHFU_EM_NO_MOVE. Its keys, travel
* samples and lunge are copied into the pools unless a pool holds that run already, anywhere,
* word for word (a lunge byte for byte): then the slot points there. 0 for a bad slot or no room
* left in a pool for what is new. */
* samples, code turn and lunge are copied into the pools unless a pool holds that run already,
* anywhere, value for value (a lunge byte for byte): then the slot points there. 0 for a bad slot
* or no room left in a pool for what is new. */
int mhfu_em_move(int slot, const mhfu_move_t *mv, const mhfu_steer_spec_t *s, uint8_t after);
/* Plays slot on entity at its next AI step, forced past the notice wait (MOVE.FORCE) or not; 0
* while nothing is wrapped or the slot is empty. */
/* Combo `id` (MHFU_EM_MOVES..255): an enter of it plays slot `first` on the hub, then `then` in
* place, as the AFTER of a move is (mhfu_em_chain), once `first` ends. 0 for an id that is no
* combo's, a slot with no move, or a `first` that does not ride the hub; the check is here, where
* a port registers, and the enter-action stub never trusts an id. Taken in place of `first`'s
* AFTER, if it has one, and only by this play: any other play, a reaction, a stop or a
* replacement of `first` drops it. Cleared by mhfu_em_moves_clear. */
int mhfu_em_combo(int id, int first, int then);
/* Plays slot (or combo id) on entity at its next AI step, forced past the notice wait
* (MOVE.FORCE) or not; 0 while nothing is wrapped or the slot is empty or the combo none. */
int mhfu_em_play(uint32_t entity, int slot, int force);
/* The slot of the move the move player runs (entering or playing), if it came from here; -1. */
int mhfu_em_playing(void);
/* The move player's step ended and asks for the successor in place: the playing slot's AFTER into
* *mv and *steer (the registry's scratch), taken and counted as a play, when it rides the same
* carrier and the move has no back pair of its own; else 0, and the old way follows (the carrier
* is entered, and the brain plays the AFTER once the move ends into it). The one place a
* successor is chosen. */
int mhfu_em_chain(uint32_t entity, mhfu_move_t *mv, const mhfu_steer_spec_t **steer);
/* The registry, or 0 before the framework's init. */
const volatile mhfu_em_moves_t *mhfu_em_moves(void);

Expand Down
17 changes: 16 additions & 1 deletion framework/include/mhfu/move.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
* clears the wake for a move on the hub or during the notice, and a move with HOLD_SCRIPT clears
* it where it enters its back pair, so the script resumes only when the carrier hands back.
*
* A move that ends with a successor on its carrier (em_vhook's AFTER, mhfu_em_chain) hands over in
* place: the end's attacks are ended and the successor's entry goes to the executor in the same AI
* frame, before the host step, so the carrier's handler (the hub's clip) never runs between the two.
* The steering carries over (steer.h): the successor keeps the entity's own YAW, and the turn the
* clips' facings make is blended out over the successor's blend. A move with AIM_OUT also leaves the
* angle still to turn to the target at the switch, which the successor turns into its own YAW
* linearly over its first AIM_OUT AI frames, on top of its own steering.
*
* A move asked for while the monster's notice runs (the player's ENTITY.AWARE bit set,
* COMBAT_MODE still 0: the roar, the hub, the AI script's combat entry) waits for it, up to
* MHFU_MOVE_WAIT AI frames, unless the call FORCEs it. One asked for while the tail cut waits for
Expand Down Expand Up @@ -79,7 +87,9 @@ typedef struct {
uint8_t hold_script; /* 1: entering the back pair clears ENTITY.SCRIPT_WAKE, so the
* AI script does not pick between this move and the one that
* follows (em_vhook sets it for a move with an AFTER) */
uint8_t _pad;
uint8_t aim_out; /* AI frames; 0 none: a hand-over in place spreads the angle
* still to turn to the target over the successor's first
* aim_out AI frames (steer.h out_total) */
uint16_t start; /* clip frame the clip starts at (the executor's a3); 0 = the
* row's own, frame 0 on em75 */
uint16_t stop; /* the move ends when part's cursor reaches this clip
Expand Down Expand Up @@ -147,6 +157,11 @@ void mhfu_move_steer(const mhfu_steer_spec_t *s);
/* The block, or 0 before the framework's init. */
const volatile mhfu_move_state_t *mhfu_move_state(void);

/* Where the monster aims: the address of ENTITY.POSITION of the object ENTITY.TARGET points at
* (the player or a comrade), the player's when that is 0 or not in RAM. The one place the target
* is resolved: the move's turn and lunge, and the brain's distance and side gates, read it. */
uint32_t mhfu_move_target_pos(uint32_t entity);

/* Replaces reaction `kind` of entity with mv (and steer, NULL for none) from the next one on, until
* called again or a brain rule on the flinch is installed; mv NULL stops it. 0 while nothing is
* wrapped. */
Expand Down
Loading
Loading