Skip to content

Commit

Permalink
replaced new checks with hitstun check
Browse files Browse the repository at this point in the history
  • Loading branch information
Walnut356 committed Nov 20, 2023
1 parent 7e91e9b commit 5d31691
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 4 deletions.
77 changes: 77 additions & 0 deletions src/stats/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,79 @@ export enum State {
COMMAND_GRAB_RANGE2_END = 0x152,
}

export enum Flags {
BIT_1_1 = 1 << 0,
// Active when any absorber hitbox is active (ness down b)
ABSORB_BUBBLE = 1 << 1,
BIT_1_3 = 1 << 2,
// Active when REFLECT_BUBBLE is active, but the reflected projectile does not change ownership
// (e.g. Mewtwo side b)
REFLECT_NO_STEAL = 1 << 3,
// Active when any projectile reflect bubble is active
REFLECT_BUBBLE = 1 << 4,
BIT_1_6 = 1 << 5,
BIT_1_7 = 1 << 6,
BIT_1_8 = 1 << 7,
BIT_2_1 = 1 << 8,
BIT_2_2 = 1 << 9,
// "Active when a character recieves intangibility or invulnerability due to a subaction that
// is removed when the subaction ends" - per UnclePunch. Little else is known besides this
// description.
SUBACTION_INVULN = 1 << 10,
// Active when the character is fastfalling
FASTFALL = 1 << 11,
// Active when the character is in hitlag, and is the one being hit. Can be thought of as
// `CAN_SDI`
DEFENDER_HITLAG = 1 << 12,
// Active when the character is in hitlag
HITLAG = 1 << 13,
BIT_2_7 = 1 << 14,
BIT_2_8 = 1 << 15,
BIT_3_1 = 1 << 16,
BIT_3_2 = 1 << 17,
// Active when the character has grabbed another character and is holding them
GRAB_HOLD = 1 << 18,
BIT_3_4 = 1 << 19,
BIT_3_5 = 1 << 20,
BIT_3_6 = 1 << 21,
BIT_3_7 = 1 << 22,
// Active when the character is shielding
SHIELDING = 1 << 23,
BIT_4_1 = 1 << 24,
// Active when character is in hitstun
HITSTUN = 1 << 25,
// Dubious meaning, likely related to subframe events (per UnclePunch). Very little is known
// besides offhand remarks
HITBOX_TOUCHING_SHIELD = 1 << 26,
BIT_4_4 = 1 << 27,
BIT_4_5 = 1 << 28,
// Active when character's physical OR projectile Powershield bubble is active
POWERSHIELD_BUBBLE = 1 << 29,
BIT_4_7 = 1 << 30,
BIT_4_8 = 1 << 31,
BIT_5_1 = 1 << 32,
// Active when character is invisible due to cloaking device item/special mode toggle
CLOAKING_DEVICE = 1 << 33,
BIT_5_3 = 1 << 34,
// Active when character is follower-type (e.g. Nana)
FOLLOWER = 1 << 35,
// Character is not processed. Corresponds to Action State `Sleep` (not to be confused with
// `FURA_SLEEP` and `DAMAGE_SLEEP`)
//
// This is typically only relevant for shiek/zelda, and in doubles. When shiek is active, zelda
// will have this flag active (and vice versa). When a doubles teammate has 0 stocks, this flag
// is active as well.
//
// IMPORTANT: If this flag is active in a replay, something has gone horribly wrong. This is
// the bit checked to determine whether or not slippi records a frame event for the character
INACTIVE = 1 << 36,
BIT_5_6 = 1 << 37,
// Active when character is dead
DEAD = 1 << 38,
// Active when character is in the magnifying glass
OFFSCREEN = 1 << 39,
}

export const Timers = {
PUNISH_RESET_FRAMES: 45,
RECOVERY_RESET_FRAMES: 45,
Expand Down Expand Up @@ -342,3 +415,7 @@ export function calcDamageTaken(frame: PostFrameUpdateType, prevFrame: PostFrame

return percent - prevPercent;
}

export function isInHitstun(flags: bigint): boolean {
return (flags & BigInt(Flags.HITSTUN)) !== BigInt(0);
}
8 changes: 4 additions & 4 deletions src/stats/conversions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
isDamaged,
isGrabbed,
isInControl,
isInHitstun,
Timers,
} from "./common";
import type { StatComputer } from "./stats";
Expand Down Expand Up @@ -146,6 +147,7 @@ function handleConversionCompute(
const opntIsGrabbed = isGrabbed(oppActionStateId);
const opntIsCommandGrabbed = isCommandGrabbed(oppActionStateId);
const opntDamageTaken = prevOpponentFrame ? calcDamageTaken(opponentFrame, prevOpponentFrame) : 0;
const opntInHitstun = isInHitstun(opponentFrame.flags ?? BigInt(0));

// Keep track of whether actionState changes after a hit. Used to compute move count
// When purely using action state there was a bug where if you did two of the same
Expand All @@ -161,11 +163,9 @@ function handleConversionCompute(
state.lastHitAnimation = null;
}

const lastAttackChanged = prevPlayerFrame ? prevPlayerFrame.lastAttackLanded !== playerFrame.lastAttackLanded : false;

// If opponent took damage and was put in some kind of stun this frame, either
// start a conversion or
if (opntIsDamaged || opntIsGrabbed || opntIsCommandGrabbed || lastAttackChanged || opntDamageTaken > 3.1) {
if (opntIsDamaged || opntIsGrabbed || opntIsCommandGrabbed || opntInHitstun) {
if (!state.conversion) {
state.conversion = {
playerIndex: indices.opponentIndex,
Expand Down Expand Up @@ -223,7 +223,7 @@ function handleConversionCompute(
state.conversion.currentPercent = opponentFrame.percent ?? 0;
}

if (opntIsDamaged || opntIsGrabbed || opntIsCommandGrabbed || lastAttackChanged || opntDamageTaken > 3.1) {
if (opntIsDamaged || opntIsGrabbed || opntIsCommandGrabbed || opntInHitstun) {
// If opponent got grabbed or damaged, reset the reset counter
state.resetCounter = 0;
}
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ export type PostFrameUpdateType = {
lastHitBy: number | null;
stocksRemaining: number | null;
actionStateCounter: number | null;
flags: bigint | null;
miscActionState: number | null;
isAirborne: boolean | null;
lastGroundId: number | null;
Expand Down
12 changes: 12 additions & 0 deletions src/utils/slpReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ export function parseMessage(command: Command, payload: Uint8Array): EventPayloa
lastHitBy: readUint8(view, 0x20),
stocksRemaining: readUint8(view, 0x21),
actionStateCounter: readFloat(view, 0x22),
flags: readFlags(view, 0x26),
miscActionState: readFloat(view, 0x2b),
isAirborne: readBool(view, 0x2f),
lastGroundId: readUint16(view, 0x30),
Expand Down Expand Up @@ -649,6 +650,17 @@ function readBool(view: DataView, offset: number): boolean | null {
return !!view.getUint8(offset);
}

function readFlags(view: DataView, offset: number): bigint | null {
if (!canReadFromView(view, offset, 8)) {
return null;
}

// this overreads by 3 bytes, but those 3 bytes will always exist in any replay that has Flags,
// and we just mask off the extra that we don't need. We're essentially reading in a byte array
// so it needs to be read as little endian.
return view.getBigUint64(offset, true) & BigInt(0x0000_00ff_ffff_ffff);
}

export function getMetadata(slpFile: SlpFileType): MetadataType | null {
if (slpFile.metadataLength <= 0) {
// This will happen on a severed incomplete file
Expand Down

0 comments on commit 5d31691

Please sign in to comment.