diff --git a/dynamic/src/attack_log.rs b/dynamic/src/attack_log.rs new file mode 100644 index 0000000000..4fde2a2f2f --- /dev/null +++ b/dynamic/src/attack_log.rs @@ -0,0 +1,175 @@ +use crate::{consts::vars, VarModule}; +use smash::app::BattleObject; + +pub type StaleMoveQueue = AttackLog<4>; + +#[inline] +pub const fn pack_player_seed(player_id: u32, seed: u32) -> i32 { + let raw: u32 = ((player_id) << 16) | (seed); + raw as i32 +} + +#[inline] +pub const fn unpack_player_id(packed: i32) -> u16 { + ((packed as u32) >> 16) as u16 +} + +#[inline] +pub const fn unpack_seed(packed: i32) -> u16 { + (packed as u32 & 0xFFFF) as u16 +} + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct AttackPatternLogEntry { + /// packed: hi16 = player_id, lo16 = per-player seed + pub status_seed: i32, + pub frame_entry: u32, + pub attack_kind: u64, +} + +impl AttackPatternLogEntry { + #[inline] + pub const fn new(status_seed: i32, frame_entry: u32, attack_kind: u64) -> Self { + Self { + status_seed, + frame_entry, + attack_kind, + } + } + + #[inline] + pub const fn new_entry(player_id: u32, seed: u32, frame_entry: u32, attack_kind: u64) -> Self { + Self { + status_seed: pack_player_seed(player_id, seed), + frame_entry, + attack_kind, + } + } + + #[inline] + pub const fn zero() -> Self { + Self { + status_seed: 0, + frame_entry: 0, + attack_kind: 0, + } + } + + #[inline] + pub const fn player_id(self) -> u16 { + unpack_player_id(self.status_seed) + } + + #[inline] + pub const fn seed(self) -> u16 { + unpack_seed(self.status_seed) + } + + #[inline] + pub const fn pack_slot0(self) -> u64 { + // same-width cast + let hi = self.status_seed as u32 as u64; + let lo = self.frame_entry as u32 as u64; + (hi << 32) | lo + } + + #[inline] + pub const fn unpack_slot0(slot0: u64) -> (i32, u32) { + let hi = (slot0 >> 32) as u32 as i32; + let lo = (slot0 & 0xFFFF_FFFF) as u32; + (hi, lo) + } +} + +pub struct AttackLog; + +impl AttackLog { + pub const FIRST_ENTRY: i32 = vars::common::instance::ATTACK_LOG_ENTRIES; + pub const STRIDE: i32 = 2; // two u64 slots per entry + + #[inline] + pub const fn base(i: usize) -> i32 { + Self::FIRST_ENTRY + (i as i32) * Self::STRIDE + } + #[inline] + pub const fn slot0(i: usize) -> i32 { + Self::base(i) + } + #[inline] + pub const fn slot1(i: usize) -> i32 { + Self::base(i) + 1 + } + + #[inline] + pub unsafe fn clear(object: *mut BattleObject) { + for i in 0..N { + VarModule::set_int64(object, Self::slot0(i), 0); + VarModule::set_int64(object, Self::slot1(i), 0); + } + } + + #[inline] + pub unsafe fn set(object: *mut BattleObject, i: usize, e: AttackPatternLogEntry) { + VarModule::set_int64(object, Self::slot0(i), e.pack_slot0()); + VarModule::set_int64(object, Self::slot1(i), e.attack_kind); + } + + #[inline] + pub unsafe fn get(object: *mut BattleObject, i: usize) -> AttackPatternLogEntry { + let s0 = VarModule::get_int64(object, Self::slot0(i)); + let attack_kind = VarModule::get_int64(object, Self::slot1(i)); + let (status_seed, frame_entry) = AttackPatternLogEntry::unpack_slot0(s0); + AttackPatternLogEntry { + status_seed, + frame_entry, + attack_kind, + } + } + + pub unsafe fn check_stale(object: *mut BattleObject, entry: AttackPatternLogEntry) -> bool { + let entry_player = entry.player_id(); + let entry_seed = entry.seed(); + let entry_attack = entry.attack_kind; + + let mut oldest_index: usize = 0; + let mut oldest_frame: u32 = u32::MAX; + + for i in 0..N { + let e = Self::get(object, i); + + // Take first empty slot + if e.frame_entry == 0 { + Self::set(object, i, entry); + return false; + } + + // Track oldest entry + if e.frame_entry < oldest_frame { + oldest_frame = e.frame_entry; + oldest_index = i; + } + + // Check player + if e.player_id() != entry_player { + //println!("Skipping!"); + continue; + } + + // Same attack seed, refresh frame counter + if e.seed() == entry_seed { + // refresh with new frame + Self::set(object, i, entry); + return false; + } + + // New attack seed, check attack staleness + if e.attack_kind == entry_attack { + Self::set(object, i, entry); + return true; + } + } + // Seed and attack are new. Overwrite oldest. + Self::set(object, oldest_index, entry); + false + } +} diff --git a/dynamic/src/consts.rs b/dynamic/src/consts.rs index 3f349bfa6b..a060744b7b 100644 --- a/dynamic/src/consts.rs +++ b/dynamic/src/consts.rs @@ -240,6 +240,10 @@ pub mod vars { pub const ATTACK_LR_CHECK: i32 = 0x0014; + // pub const DI_STALE_LEVEL: i32 = 0x0015; + pub const ATTACK_LOG_SEED: i32 = 0x0016; + pub const POWER_UP_BIT_LAST: i32 = 0x0017; + // floats pub const LAST_ATTACK_DAMAGE_DEALT: i32 = 0x0000; @@ -282,6 +286,10 @@ pub mod vars { pub const LAST_RECEIVED_ATTACK_HIT_LOCATION_Y: i32 = 0x0024; pub const LAST_RECEIVED_ATTACK_HIT_LOCATION_Z: i32 = 0x0025; pub const LAND_CANCEL_LAG: i32 = 0x0026; + + // int64 + pub const ATTACK_LOG_ENTRIES: i32 = 0x0050; // Length(4) * int64 + // Padding until 60 } pub mod status { // flags diff --git a/dynamic/src/ext.rs b/dynamic/src/ext.rs index ae813219c5..afb4500401 100644 --- a/dynamic/src/ext.rs +++ b/dynamic/src/ext.rs @@ -1,4 +1,5 @@ use crate::consts::{globals::*, vars}; +use crate::attack_log::{AttackPatternLogEntry, StaleMoveQueue}; use bitflags::bitflags; use modular_bitfield::specifiers::*; use smash::app::{ @@ -550,6 +551,9 @@ pub trait BomaExt { unsafe fn sub_check_command_parry(&mut self) -> L2CValue; // Checks for situation kind and transitions to heavy landing unsafe fn check_land_cancel(&mut self, landing_lag: Option) -> bool; + // Checks for stale move queue + unsafe fn check_stale_move_entry(&mut self, entry: AttackPatternLogEntry) -> bool; + unsafe fn reset_stale_move_log(&mut self); /// check for hitfall (should be called once per frame) unsafe fn check_hitfall(&mut self) -> bool; @@ -1284,6 +1288,16 @@ impl BomaExt for BattleObjectModuleAccessor { false } + // Check if move is stale, adds entry if needed. + unsafe fn check_stale_move_entry(&mut self, entry: AttackPatternLogEntry) -> bool { + StaleMoveQueue::check_stale(self.object(), entry) + } + + unsafe fn reset_stale_move_log(&mut self) { + StaleMoveQueue::clear(self.object()); + } + + /// Sets the position of the front/red ledge-grab box (see [`set_center_cliff_hangdata`](BomaExt::set_center_cliff_hangdata) for more information) /// /// # Arguments diff --git a/dynamic/src/game_modes.rs b/dynamic/src/game_modes.rs index fb4d40b0e8..fca294667d 100644 --- a/dynamic/src/game_modes.rs +++ b/dynamic/src/game_modes.rs @@ -11,6 +11,7 @@ pub enum CustomMode { Smash64Mode = 4, MagicSeriesMode = 5, ElementMode = 6, + //RainbowDI = } impl fmt::Display for CustomMode { @@ -23,6 +24,7 @@ impl fmt::Display for CustomMode { CustomMode::Smash64Mode => write!(f, "Smash64"), CustomMode::MagicSeriesMode => write!(f, "MagicSeries"), CustomMode::ElementMode => write!(f, "Element"), + //CustomMode::ElementMode => write!(f, "RainbowDI"), } } } diff --git a/dynamic/src/lib.rs b/dynamic/src/lib.rs index 85eafddffa..99d2d98bd0 100644 --- a/dynamic/src/lib.rs +++ b/dynamic/src/lib.rs @@ -10,6 +10,7 @@ pub mod frame_info; pub mod game_modes; pub mod ui; pub mod se; +pub mod attack_log; #[macro_use] extern crate modular_bitfield; diff --git a/fighters/common/src/function_hooks/attack.rs b/fighters/common/src/function_hooks/attack.rs index f4b43cad2d..3d41c28c73 100644 --- a/fighters/common/src/function_hooks/attack.rs +++ b/fighters/common/src/function_hooks/attack.rs @@ -1,4 +1,5 @@ use super::*; +use crate::attack_log::AttackPatternLogEntry; use smash_rs::app::CollisionSoundAttr; use utils::ext::*; use utils::game_modes::CustomMode; @@ -305,13 +306,38 @@ unsafe fn x03df93c(ctx: &mut skyline::hooks::InlineCtx) { unsafe fn notify_log_event_collision_hit(fighter_manager: u64, attacker_object_id: u32, receiver_object_id: u32, move_type: u64, arg5: u64, move_type_again: u64) -> u64 { let attacker_boma = &mut *smash::app::sv_battle_object::module_accessor(attacker_object_id); let receiver_boma = &mut *smash::app::sv_battle_object::module_accessor(receiver_object_id); + let atk_has_var = VarModule::has_var_module(attacker_boma.object()); + let rcv_has_var = VarModule::has_var_module(receiver_boma.object()); - if VarModule::has_var_module(attacker_boma.object()) + if atk_has_var && VarModule::is_flag(attacker_boma.object(), vars::common::status::HIT_EFFECT_DROP_ITEM) && ItemModule::is_have_item(receiver_boma, 0) { ItemModule::drop_item(receiver_boma, 90.0, 0.0, 0); } + // Add Attack to Stale Move Log + // TODO: Projectile Compatibility + if (rcv_has_var && atk_has_var && attacker_boma.is_fighter() && receiver_boma.is_fighter()) { + let attack_seed = VarModule::get_int(attacker_boma.object(), vars::common::instance::ATTACK_LOG_SEED); + let attack_kind = smash::app::sv_battle_object::log_attack_kind(attacker_object_id); + let frame = util::get_global_frame_count() as u32; + let log = AttackPatternLogEntry::new_entry( + attacker_boma.get_player_idx_from_boma() as u32, + attack_seed as u32, + frame, + attack_kind, + ); + + // Rainbow DI Staling + // if receiver_boma.check_stale_move_entry(log) { + // let mut new_stale_level = + // VarModule::get_int(receiver_boma.object(), vars::common::instance::DI_STALE_LEVEL) + 1; + // new_stale_level = std::cmp::min(new_stale_level, 2); + // VarModule::set_int(receiver_boma.object(), vars::common::instance::DI_STALE_LEVEL, new_stale_level); + // } + + } + original!()(fighter_manager, attacker_object_id, receiver_object_id, move_type, arg5, move_type_again) } diff --git a/fighters/common/src/function_hooks/get_param.rs b/fighters/common/src/function_hooks/get_param.rs index 6b56e249c1..104dca7c11 100644 --- a/fighters/common/src/function_hooks/get_param.rs +++ b/fighters/common/src/function_hooks/get_param.rs @@ -187,6 +187,20 @@ pub unsafe fn get_param_float_hook(x0 /*boma*/: u64, x1 /*param_type*/: u64, x2 return 4.0; } } + // if modes.contains(&CustomMode::RainbowDI) { + // // Correction based on Max DI Level + // if x2 == hash40("damage_fly_correction_max") { + // let bo = boma_reference.object(); + // let di_level = VarModule::get_int(bo, vars::common::instance::DI_STALE_LEVEL); + // //println!("Real DI Correction: {} \n", di_level); + // match di_level { + // 1 => return 17.0, + // 2 => return 19.0, + // _ => return original!()(x0, x1, x2), + // }; + // return original!()(x0, x1, x2); + // } + // } }, _ => {} } @@ -207,6 +221,7 @@ pub unsafe fn get_param_float_hook(x0 /*boma*/: u64, x1 /*param_type*/: u64, x2 } */ + // handle reduction of the tumble threshold for DK when in barrel carry if x2 == hash40("damage_level3") && boma_reference.kind() == *FIGHTER_KIND_DONKEY diff --git a/fighters/common/src/function_hooks/mod.rs b/fighters/common/src/function_hooks/mod.rs index 645e590bf7..127fe7a14c 100644 --- a/fighters/common/src/function_hooks/mod.rs +++ b/fighters/common/src/function_hooks/mod.rs @@ -815,7 +815,7 @@ pub fn install() { jumps::install(); knockback::install(); stage_hazards::install(); - //set_fighter_status_data::install(); + set_fighter_status_data::install(); attack::install(); collision::install(); camera::install(); diff --git a/fighters/common/src/function_hooks/set_fighter_status_data.rs b/fighters/common/src/function_hooks/set_fighter_status_data.rs index 0ed4664c8c..ff504d16b4 100644 --- a/fighters/common/src/function_hooks/set_fighter_status_data.rs +++ b/fighters/common/src/function_hooks/set_fighter_status_data.rs @@ -1,16 +1,27 @@ use super::*; use globals::*; +const MAX_SEED: u32 = 12_000; + //================================================================= //== FighterStatusModuleImpl::set_fighter_status_data //================================================================= -// #[skyline::hook(replace=FighterStatusModuleImpl::set_fighter_status_data)] -// unsafe fn set_fighter_status_data_hook(boma: &mut BattleObjectModuleAccessor, arg2: bool, treaded_kind: i32, arg4: bool, arg5: bool, arg6: bool, log_mask_flag: u64, status_attr: u32, power_up_attack_bit: u32, arg10: u32) { -// original!()(boma, arg2, treaded_kind, arg4, arg5, arg6, log_mask_flag, new_status_attr, power_up_attack_bit, arg10) -// } +#[skyline::hook(replace=FighterStatusModuleImpl::set_fighter_status_data)] +unsafe fn set_fighter_status_data_hook(boma: &mut BattleObjectModuleAccessor, arg2: bool, treaded_kind: i32, arg4: bool, arg5: bool, arg6: bool, log_mask_flag: u64, status_attr: u32, power_up_attack_bit: u32, arg10: u32) { + let bo = boma.object(); + if (VarModule::has_var_module(bo)) { + let bit_last = VarModule::get_int(bo, vars::common::instance::POWER_UP_BIT_LAST) as u32; + if (power_up_attack_bit != bit_last) { + GLOBAL_SEED += 1; + let new_seed = GLOBAL_SEED % MAX_SEED; + VarModule::set_int(boma.object(), vars::common::instance::ATTACK_LOG_SEED, new_seed as i32); + //println!("Seed Update {} | BIT: {} \n", new_seed, power_up_attack_bit); + } + VarModule::set_int(boma.object(), vars::common::instance::POWER_UP_BIT_LAST, power_up_attack_bit as i32); + } + return original!()(boma, arg2, treaded_kind, arg4, arg5, arg6, log_mask_flag, status_attr, power_up_attack_bit, arg10); +} pub fn install() { - // skyline::install_hooks!( - // set_fighter_status_data_hook, - // ); -} \ No newline at end of file + skyline::install_hooks!(set_fighter_status_data_hook); +} diff --git a/fighters/common/src/general_statuses/mod.rs b/fighters/common/src/general_statuses/mod.rs index d1c5ef230b..81395b0da9 100644 --- a/fighters/common/src/general_statuses/mod.rs +++ b/fighters/common/src/general_statuses/mod.rs @@ -935,16 +935,34 @@ pub unsafe fn FighterStatusDamage__correctDamageVector(fighter: &mut L2CFighterC #[skyline::hook(replace = smash::lua2cpp::L2CFighterCommon_FighterStatusDamage__correctDamageVectorEffect)] pub unsafe fn FighterStatusDamage__correctDamageVectorEffect(fighter: &mut L2CFighterCommon, param_1: L2CValue) -> L2CValue { + //let rainbow_di = false; match utils::game_modes::get_custom_mode() { Some(modes) => { if modes.contains(&CustomMode::Smash64Mode) { return 0.into(); } + // if modes.contains(&CustomMode::RainbowDI) { + // rainbow_di = true; + // } }, _ => {} } if fighter.global_table[STATUS_KIND_INTERRUPT] != FIGHTER_STATUS_KIND_DAMAGE_AIR { - return call_original!(fighter, param_1); + let ret = call_original!(fighter, param_1); + + // if rainbow_di { + // let boma = fighter.boma(); + // let bo = fighter.battle_object; + // let handle = fighter.get_int(*FIGHTER_STATUS_DAMAGE_WORK_INT_CORRECT_DAMAGE_VECTOR_EFFECT_ID) as u32; + // let di_level = VarModule::get_int(bo, vars::common::instance::DI_STALE_LEVEL); + // match di_level { + // 1 => EffectModule::set_rgb(boma, handle, 0.05, 0.65, 0.75), // Cyan + // 2 => EffectModule::set_rgb(boma, handle, 1.5, 0.1, 0.55), // Purple + // _ => EffectModule::set_rgb(boma, handle, 0.1, 0.9, 0.1), // Green + // } + // } + + return ret; } // This allows us to call the blue DI line effect on non-tumble knockback // Currently not able to be done by reimplementing this function @@ -952,6 +970,17 @@ pub unsafe fn FighterStatusDamage__correctDamageVectorEffect(fighter: &mut L2CFi // which is not currently supported by skyline-smash fighter.global_table[STATUS_KIND_INTERRUPT].assign(&L2CValue::I32(*FIGHTER_STATUS_KIND_DAMAGE_FLY)); let ret = call_original!(fighter, param_1); + // if rainbow_di { + // let boma = fighter.boma(); + // let bo = fighter.battle_object; + // let handle = fighter.get_int(*FIGHTER_STATUS_DAMAGE_WORK_INT_CORRECT_DAMAGE_VECTOR_EFFECT_ID) as u32; + // let di_level = VarModule::get_int(bo, vars::common::instance::DI_STALE_LEVEL); + // match di_level { + // 1 => EffectModule::set_rgb(boma, handle, 0.05, 0.65, 0.75), // Cyan + // 2 => EffectModule::set_rgb(boma, handle, 1.5, 0.1, 0.55), // Purple + // _ => EffectModule::set_rgb(boma, handle, 0.1, 0.9, 0.1), // Green + // } + // } fighter.global_table[STATUS_KIND_INTERRUPT].assign(&L2CValue::I32(*FIGHTER_STATUS_KIND_DAMAGE_AIR)); ret } diff --git a/fighters/common/src/lib.rs b/fighters/common/src/lib.rs index f374028193..9e943ba791 100644 --- a/fighters/common/src/lib.rs +++ b/fighters/common/src/lib.rs @@ -34,10 +34,15 @@ pub mod acmd; // for storing what team color the last attacker had. used in a couple different common files pub static mut LAST_ATTACK_TEAM_COLOR: i32 = 0; +// for stale move seeds upon state changes +pub static mut GLOBAL_SEED: u32 = 0; extern "C" fn common_init(fighter: &mut L2CFighterCommon) { VarModule::set_int(fighter.battle_object, vars::common::instance::LEDGE_ID, -1); VarModule::off_flag(fighter.battle_object, vars::common::instance::IS_INIT); + unsafe { + GLOBAL_SEED = 0; + } } pub fn install() { diff --git a/fighters/common/src/opff/var_resets.rs b/fighters/common/src/opff/var_resets.rs index 33250b5957..d508a24878 100644 --- a/fighters/common/src/opff/var_resets.rs +++ b/fighters/common/src/opff/var_resets.rs @@ -120,6 +120,27 @@ unsafe fn var_resets(boma: &mut BattleObjectModuleAccessor) { if VarModule::get_int(boma.object(), vars::common::instance::FLY_NEXT_FRAME) > 0 { VarModule::dec_int(boma.object(), vars::common::instance::FLY_NEXT_FRAME); } + + // Rainbow DI + // match utils::game_modes::get_custom_mode() { + // Some(modes) => { + // if modes.contains(&CustomMode::RainbowDI) { + // // Stale Move Log Reset + // if (boma.is_status_one_of(death_statuses)) { + // //println!("resetting stale moves"); + // boma.reset_stale_move_log(); + // } + + // // Max DI Correction Level Reset + // if boma.is_situation(*SITUATION_KIND_GROUND) && (!boma.is_status_one_of(death_statuses) && !boma.is_status_one_of(damage_statuses) && !boma.is_in_hitlag() && boma.is_fighter()) { + // VarModule::set_int(boma.object(), vars::common::instance::DI_STALE_LEVEL, 0); + // } + // } + // }, + // _ => {} + // } + + } pub unsafe fn run(boma: &mut BattleObjectModuleAccessor, cat: [i32 ; 4], status_kind: i32, situation_kind: i32, fighter_kind: i32, stick_x: f32, stick_y: f32, facing: f32) { diff --git a/romfs/source/fighter/common/param/common.prcxml b/romfs/source/fighter/common/param/common.prcxml index e65179d428..127da632a8 100644 --- a/romfs/source/fighter/common/param/common.prcxml +++ b/romfs/source/fighter/common/param/common.prcxml @@ -8,7 +8,7 @@ 0.716 4 6 - 2 + 2 1 1 -1 @@ -67,14 +67,15 @@ 999 2.5 1 + 1 - dummy - dummy - 0.075 - 0.060 - 0.045 - 0.030 - 0.015 + 0.00 + 0.00 + 0.00 + 0.00 + 0.00 + 0.00 + 0.00 0.00 0.00 0.00 diff --git a/romfs/source/fighter/common/param/effect.prcxml b/romfs/source/fighter/common/param/effect.prcxml index 98e1cc24b5..ae0bba13c2 100644 --- a/romfs/source/fighter/common/param/effect.prcxml +++ b/romfs/source/fighter/common/param/effect.prcxml @@ -14,4 +14,4 @@ invalid - \ No newline at end of file +