1
0
mirror of https://github.com/jugeeya/UltimateTrainingModpack.git synced 2026-01-22 10:20:24 +00:00

Fix inaccurate Frame Advantage (#752)

This commit is contained in:
asimon-1
2025-11-17 06:14:38 -08:00
committed by GitHub
parent 235560ee71
commit dc3eab58d8
2 changed files with 62 additions and 87 deletions

View File

@@ -1,9 +1,10 @@
use skyline::nn::ui2d::ResColor;
use smash::app::lua_bind::{CancelModule, StatusModule, WorkModule};
use smash::app::lua_bind::{AttackModule, CancelModule, StatusModule, WorkModule};
use smash::app::BattleObjectModuleAccessor;
use smash::lib::lua_const::*;
use crate::consts::Action;
use crate::info;
use crate::training::frame_counter;
use crate::training::ui::notifications;
use crate::try_get_module_accessor;
@@ -13,31 +14,13 @@ use training_mod_sync::*;
static PLAYER_WAS_ACTIONABLE: RwLock<bool> = RwLock::new(false);
static CPU_WAS_ACTIONABLE: RwLock<bool> = RwLock::new(false);
static IS_COUNTING: RwLock<bool> = RwLock::new(false);
static PLAYER_FRAME_COUNTER_INDEX: LazyLock<usize> =
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
static CPU_FRAME_COUNTER_INDEX: LazyLock<usize> =
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
unsafe fn was_in_hitstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
let prev_status = StatusModule::prev_status_kind(module_accessor, 0);
(*FIGHTER_STATUS_KIND_DAMAGE..*FIGHTER_STATUS_KIND_DAMAGE_FALL).contains(&prev_status)
}
unsafe fn is_in_hitstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
(*FIGHTER_STATUS_KIND_DAMAGE..*FIGHTER_STATUS_KIND_DAMAGE_FALL)
.contains(&StatusModule::status_kind(module_accessor))
}
unsafe fn was_in_shieldstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
let prev_status = StatusModule::prev_status_kind(module_accessor, 0);
prev_status == FIGHTER_STATUS_KIND_GUARD_DAMAGE
}
unsafe fn is_in_shieldstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
StatusModule::status_kind(module_accessor) == FIGHTER_STATUS_KIND_GUARD_DAMAGE
}
unsafe fn is_actionable(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
[
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_ESCAPE_AIR, // Airdodge
@@ -54,12 +37,11 @@ unsafe fn is_actionable(module_accessor: *mut BattleObjectModuleAccessor) -> boo
fn update_frame_advantage(frame_advantage: i32) {
if read(&MENU).frame_advantage == OnOff::ON {
// Prioritize Frame Advantage over Input Recording Playback
notifications::clear_notification("Input Recording");
notifications::clear_notification("Frame Advantage");
// Prioritize notifications for Frame Advantage
notifications::clear_all_notifications();
notifications::color_notification(
"Frame Advantage".to_string(),
format!("{frame_advantage}"),
format!("{frame_advantage:+}"),
60,
match frame_advantage {
x if x < 0 => ResColor {
@@ -87,8 +69,9 @@ fn update_frame_advantage(frame_advantage: i32) {
pub unsafe fn once_per_frame(module_accessor: &mut BattleObjectModuleAccessor) {
// Skip the CPU so we don't run twice per frame
// Also skip if the CPU is set to mash since that interferes with the frame calculation
let entry_id_int = WorkModule::get_int(module_accessor, *FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID);
if entry_id_int != (FighterId::Player as i32) {
if entry_id_int != (FighterId::Player as i32) || read(&MENU).mash_state != Action::empty() {
return;
}
let player_module_accessor = try_get_module_accessor(FighterId::Player)
@@ -101,76 +84,68 @@ pub unsafe fn once_per_frame(module_accessor: &mut BattleObjectModuleAccessor) {
let cpu_is_actionable = is_actionable(cpu_module_accessor);
let cpu_was_actionable = read(&CPU_WAS_ACTIONABLE);
let cpu_just_actionable = !cpu_was_actionable && cpu_is_actionable;
let is_counting = frame_counter::is_counting(*PLAYER_FRAME_COUNTER_INDEX)
|| frame_counter::is_counting(*CPU_FRAME_COUNTER_INDEX);
if !is_counting {
if read(&MENU).mash_state == Action::empty()
&& !player_is_actionable
&& !cpu_is_actionable
&& (!was_in_shieldstun(cpu_module_accessor) && is_in_shieldstun(cpu_module_accessor)
|| (!was_in_hitstun(cpu_module_accessor) && is_in_hitstun(cpu_module_accessor)))
// Lock in frames
if cpu_just_actionable {
frame_counter::stop_counting(*CPU_FRAME_COUNTER_INDEX);
}
if player_just_actionable {
frame_counter::stop_counting(*PLAYER_FRAME_COUNTER_INDEX);
}
// DEBUG LOGGING
// if read(&IS_COUNTING) {
// if player_is_actionable && cpu_is_actionable {
// info!("!");
// } else if !player_is_actionable && cpu_is_actionable {
// info!("-");
// } else if player_is_actionable && !cpu_is_actionable {
// info!("+");
// } else {
// info!(".");
// }
// }
if !player_is_actionable && !cpu_is_actionable {
if AttackModule::is_infliction(
player_module_accessor,
*COLLISION_KIND_MASK_HIT | *COLLISION_KIND_MASK_SHIELD,
) || StatusModule::status_kind(player_module_accessor) == *FIGHTER_STATUS_KIND_THROW
{
// Start counting when:
// 1. We have no mash option selected AND
// 2. Neither fighter is currently actionable AND
// 3. Either
// a. the CPU has just entered shieldstun
// b. the CPU has just entered hitstun
//
// If a mash option is selected, this can interfere with our ability to determine when
// a character becomes actionable. So don't ever start counting if we can't reliably stop.
//
// Since our "just_actionable" checks assume that neither character is already actionable,
// we need to guard against instances where the player is already actionable by the time that
// the CPU get hit, such as if the player threw a projectile from far away.
// Otherwise our "just_actionable" checks are not valid.
//
// We also need to guard against instances where the CPU's status is in hitstun but they are actually actionable.
// I dunno, makes no sense to me either. Can trigger this edge case with PAC-MAN jab 1 against Lucas at 0%.
// This shows up as the count restarting immediately after the last one ended.
if !read(&IS_COUNTING) {
// Start counting when the player lands a hit
info!("Starting frame counter");
} else {
// Note that we want the same behavior even if we are already counting!
// This prevents multihit moves which aren't true combos from miscounting
// from the first hit (e.g. Pikachu back air on shield)
info!("Restarting frame counter");
}
frame_counter::reset_frame_count(*PLAYER_FRAME_COUNTER_INDEX);
frame_counter::reset_frame_count(*CPU_FRAME_COUNTER_INDEX);
frame_counter::start_counting(*PLAYER_FRAME_COUNTER_INDEX);
frame_counter::start_counting(*CPU_FRAME_COUNTER_INDEX);
assign(&IS_COUNTING, true);
}
} else {
// Uncomment this if you want some frame logging
// if (player_is_actionable && cpu_is_actionable) {
// info!("!");
// } else if (!player_is_actionable && cpu_is_actionable) {
// info!("-");
// } else if (player_is_actionable && !cpu_is_actionable) {
// info!("+");
// } else {
// info!(".");
// }
// Stop counting as soon as each fighter becomes actionable
if player_just_actionable {
frame_counter::stop_counting(*PLAYER_FRAME_COUNTER_INDEX);
}
if cpu_just_actionable {
frame_counter::stop_counting(*CPU_FRAME_COUNTER_INDEX);
}
// If we just finished counting for the second fighter, then display frame advantage
if !frame_counter::is_counting(*PLAYER_FRAME_COUNTER_INDEX)
&& !frame_counter::is_counting(*CPU_FRAME_COUNTER_INDEX)
&& (player_just_actionable || cpu_just_actionable)
{
update_frame_advantage(
frame_counter::get_frame_count(*CPU_FRAME_COUNTER_INDEX) as i32
- frame_counter::get_frame_count(*PLAYER_FRAME_COUNTER_INDEX) as i32,
} else if player_is_actionable && cpu_is_actionable {
if read(&IS_COUNTING) {
let frame_advantage = frame_counter::get_frame_count(*CPU_FRAME_COUNTER_INDEX) as i32
- frame_counter::get_frame_count(*PLAYER_FRAME_COUNTER_INDEX) as i32;
info!(
"Stopping frame counter, frame advantage: {}",
frame_advantage
);
// Frame counters should reset before we start again, but reset them just to be safe
update_frame_advantage(frame_advantage);
frame_counter::reset_frame_count(*PLAYER_FRAME_COUNTER_INDEX);
frame_counter::reset_frame_count(*CPU_FRAME_COUNTER_INDEX);
};
// Store the current actionability state for next frame
assign(&PLAYER_WAS_ACTIONABLE, player_is_actionable);
assign(&CPU_WAS_ACTIONABLE, cpu_is_actionable);
assign(&IS_COUNTING, false);
}
} else {
// No need to start or stop counting, one of the fighters is still not actionable
}
assign(&CPU_WAS_ACTIONABLE, cpu_is_actionable);
assign(&PLAYER_WAS_ACTIONABLE, player_is_actionable);
}

View File

@@ -37,7 +37,7 @@ pub fn stop_counting(index: usize) {
(*counters_lock)[index].should_count = false;
}
pub fn is_counting(index: usize) -> bool {
pub fn _is_counting(index: usize) -> bool {
let counters_lock = lock_read(&COUNTERS);
(*counters_lock)[index].should_count
}