diff --git a/ryujinx_build.sh b/ryujinx_build.sh index ec6fe73..af46fcb 100644 --- a/ryujinx_build.sh +++ b/ryujinx_build.sh @@ -1,7 +1,7 @@ set -eu # Obviously adjust these based on your paths -RYUJINX_APPLICATION_PATH="/mnt/c/Users/Jdsam/Downloads/ryujinx-Release-1.0.0+ba3ae74-win_x64/Ryujinx.exe" +RYUJINX_APPLICATION_PATH="/mnt/c/Users/Jdsam/Downloads/ryujinx-1.1.119-win_x64/publish/Ryujinx.exe" SMASH_APPLICATION_PATH="C:\Users\Jdsam\Downloads\Super Smash Bros. Ultimate (World) (En,Ja,Fr,De,Es,It,Nl,Zh-Hant,Zh-Hans,Ko,Ru)\Super Smash Bros. Ultimate (World) (En,Ja,Fr,De,Es,It,Nl,Zh-Hant,Zh-Hans,Ko,Ru).xci" RYUJINX_SMASH_SKYLINE_PLUGINS_PATH="/mnt/c/Users/Jdsam/AppData/Roaming/Ryujinx/mods/contents/01006a800016e000/romfs/skyline/plugins" diff --git a/src/common/mod.rs b/src/common/mod.rs index 855ecf5..ae1dbee 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -12,6 +12,7 @@ pub use crate::common::consts::MENU; pub static mut DEFAULTS_MENU: TrainingModpackMenu = crate::common::consts::DEFAULTS_MENU; pub static mut BASE_MENU: TrainingModpackMenu = unsafe { DEFAULTS_MENU }; pub static mut FIGHTER_MANAGER_ADDR: usize = 0; +pub static mut ITEM_MANAGER_ADDR: usize = 0; pub static mut STAGE_MANAGER_ADDR: usize = 0; #[cfg(not(feature = "outside_training_mode"))] diff --git a/src/lib.rs b/src/lib.rs index 48a5873..dbbe7ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -166,7 +166,6 @@ pub fn main() { received_input = true; }); let b_press = &mut button_presses.b; - let b_prev_press = b_press.prev_frame_is_pressed; b_press.read_press().then(|| { received_input = true; if !app.outer_list { diff --git a/src/training/character_specific/items.rs b/src/training/character_specific/items.rs new file mode 100644 index 0000000..f18a256 --- /dev/null +++ b/src/training/character_specific/items.rs @@ -0,0 +1,553 @@ +use crate::common::consts::*; +use crate::common::*; +use crate::training::mash; +use smash::app; +use smash::app::lua_bind::*; +use smash::app::ItemKind; +use smash::app::{ArticleOperationTarget, BattleObjectModuleAccessor, Item}; +use smash::cpp::l2c_value::LuaConst; +use smash::lib::lua_const::*; + +pub struct CharItem { + pub fighter_kind: LuaConst, + pub item_kind: Option, + pub article_kind: Option, + pub variation: Option, +} + +pub const ALL_CHAR_ITEMS: [CharItem; 45] = [ + CharItem { + fighter_kind: FIGHTER_KIND_DIDDY, + item_kind: None, + article_kind: Some(FIGHTER_DIDDY_GENERATE_ARTICLE_ITEM_BANANA), + variation: None, + }, + CharItem { + // Robin Tome + fighter_kind: FIGHTER_KIND_REFLET, + item_kind: Some(ITEM_KIND_BOOK), + article_kind: None, + variation: None, // TODO: Look at the lua const ITEM_BOOK_STATUS_KIND_BEFORE_BORN + }, + CharItem { + // Banjo-Kazooie Grenade Egg + fighter_kind: FIGHTER_KIND_BUDDY, + item_kind: Some(ITEM_KIND_BUDDYBOMB), + article_kind: None, + variation: None, + }, + CharItem { + // Turnip + fighter_kind: FIGHTER_KIND_DAISY, + item_kind: None, + article_kind: Some(FIGHTER_DAISY_GENERATE_ARTICLE_DAIKON), + variation: Some(ITEM_VARIATION_DAISYDAIKON_1), // Smile + }, + CharItem { + // Turnip + fighter_kind: FIGHTER_KIND_DAISY, + item_kind: None, + article_kind: Some(FIGHTER_DAISY_GENERATE_ARTICLE_DAIKON), + variation: Some(ITEM_VARIATION_DAISYDAIKON_6), // Winky + }, + CharItem { + // Turnip + fighter_kind: FIGHTER_KIND_DAISY, + item_kind: None, + article_kind: Some(FIGHTER_DAISY_GENERATE_ARTICLE_DAIKON), + variation: Some(ITEM_VARIATION_DAISYDAIKON_7), // Dot-Eyes + }, + CharItem { + // Turnip + fighter_kind: FIGHTER_KIND_DAISY, + item_kind: None, + article_kind: Some(FIGHTER_DAISY_GENERATE_ARTICLE_DAIKON), + variation: Some(ITEM_VARIATION_DAISYDAIKON_8), // Stitch-face + }, + CharItem { + // Mr Saturn + fighter_kind: FIGHTER_KIND_DAISY, + item_kind: Some(ITEM_KIND_DOSEISAN), + article_kind: None, + variation: None, + }, + CharItem { + // Bob-omb + fighter_kind: FIGHTER_KIND_DAISY, + item_kind: Some(ITEM_KIND_BOMBHEI), + article_kind: None, + variation: Some(ITEM_VARIATION_BOMBHEI_NORMAL), + }, + CharItem { + fighter_kind: FIGHTER_KIND_DIDDY, + item_kind: Some(ITEM_KIND_DIDDYPEANUTS), + article_kind: None, + variation: None, + }, + CharItem { + // Sheik Sideb Bomb + fighter_kind: FIGHTER_KIND_SHEIK, + item_kind: Some(ITEM_KIND_EXPLOSIONBOMB), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_KROOL, + item_kind: Some(ITEM_KIND_KROOLCROWN), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_LINK, + item_kind: Some(ITEM_KIND_LINKARROW), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_LINK, + item_kind: Some(ITEM_KIND_LINKBOMB), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_KOOPAJR, + item_kind: Some(ITEM_KIND_MECHAKOOPA), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_ROCKMAN, + item_kind: Some(ITEM_KIND_METALBLADE), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_PACMAN, + item_kind: Some(ITEM_KIND_PACMANCHERRY), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_PACMAN, + item_kind: Some(ITEM_KIND_PACMANSTRAWBERRY), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_PACMAN, + item_kind: Some(ITEM_KIND_PACMANORANGE), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_PACMAN, + item_kind: Some(ITEM_KIND_PACMANAPPLE), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_PACMAN, + item_kind: Some(ITEM_KIND_PACMANMELON), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_PACMAN, + item_kind: Some(ITEM_KIND_PACMANBOSS), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_PACMAN, + item_kind: Some(ITEM_KIND_PACMANBELL), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_PACMAN, + item_kind: Some(ITEM_KIND_PACMANKEY), + article_kind: None, + variation: None, + }, + CharItem { + // Turnip + fighter_kind: FIGHTER_KIND_PEACH, + item_kind: None, + article_kind: Some(FIGHTER_PEACH_GENERATE_ARTICLE_DAIKON), + variation: Some(ITEM_VARIATION_PEACHDAIKON_1), // Smile + }, + CharItem { + // Turnip + fighter_kind: FIGHTER_KIND_PEACH, + item_kind: None, + article_kind: Some(FIGHTER_PEACH_GENERATE_ARTICLE_DAIKON), + variation: Some(ITEM_VARIATION_PEACHDAIKON_6), // Winky + }, + CharItem { + // Turnip + fighter_kind: FIGHTER_KIND_PEACH, + item_kind: None, + article_kind: Some(FIGHTER_PEACH_GENERATE_ARTICLE_DAIKON), + variation: Some(ITEM_VARIATION_PEACHDAIKON_7), // Dot-Eyes + }, + CharItem { + // Turnip + fighter_kind: FIGHTER_KIND_PEACH, + item_kind: None, + article_kind: Some(FIGHTER_PEACH_GENERATE_ARTICLE_DAIKON), + variation: Some(ITEM_VARIATION_PEACHDAIKON_8), // Stitch-face + }, + CharItem { + // Mr Saturn + fighter_kind: FIGHTER_KIND_PEACH, + item_kind: Some(ITEM_KIND_DOSEISAN), + article_kind: None, + variation: None, + }, + CharItem { + // Bob-omb + fighter_kind: FIGHTER_KIND_PEACH, + item_kind: Some(ITEM_KIND_BOMBHEI), + article_kind: None, + variation: Some(ITEM_VARIATION_BOMBHEI_NORMAL), + }, + CharItem { + fighter_kind: FIGHTER_KIND_RICHTER, + item_kind: Some(ITEM_KIND_RICHTERHOLYWATER), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_ROBOT, + item_kind: Some(ITEM_KIND_ROBOTGYRO), + article_kind: None, + variation: Some(ITEM_VARIATION_ROBOTGYRO_1P), + }, + CharItem { + fighter_kind: FIGHTER_KIND_ROBOT, + item_kind: Some(ITEM_KIND_ROBOTGYRO), + article_kind: None, + variation: Some(ITEM_VARIATION_ROBOTGYRO_2P), + }, + CharItem { + fighter_kind: FIGHTER_KIND_ROBOT, + item_kind: Some(ITEM_KIND_ROBOTGYRO), + article_kind: None, + variation: Some(ITEM_VARIATION_ROBOTGYRO_3P), + }, + CharItem { + fighter_kind: FIGHTER_KIND_ROBOT, + item_kind: Some(ITEM_KIND_ROBOTGYRO), + article_kind: None, + variation: Some(ITEM_VARIATION_ROBOTGYRO_4P), + }, + CharItem { + fighter_kind: FIGHTER_KIND_ROBOT, + item_kind: Some(ITEM_KIND_ROBOTGYRO), + article_kind: None, + variation: Some(ITEM_VARIATION_ROBOTGYRO_5P), + }, + CharItem { + fighter_kind: FIGHTER_KIND_ROBOT, + item_kind: Some(ITEM_KIND_ROBOTGYRO), + article_kind: None, + variation: Some(ITEM_VARIATION_ROBOTGYRO_6P), + }, + CharItem { + fighter_kind: FIGHTER_KIND_ROBOT, + item_kind: Some(ITEM_KIND_ROBOTGYRO), + article_kind: None, + variation: Some(ITEM_VARIATION_ROBOTGYRO_7P), + }, + CharItem { + fighter_kind: FIGHTER_KIND_ROBOT, + item_kind: Some(ITEM_KIND_ROBOTGYRO), + article_kind: None, + variation: Some(ITEM_VARIATION_ROBOTGYRO_8P), + }, + CharItem { + fighter_kind: FIGHTER_KIND_SIMON, + item_kind: Some(ITEM_KIND_SIMONHOLYWATER), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_SNAKE, + item_kind: Some(ITEM_KIND_SNAKEGRENADE), + article_kind: None, + variation: None, + }, + // CharItem { + // // Cardboard Box from Taunt + // fighter_kind: FIGHTER_KIND_SNAKE, + // item_kind: Some(ITEM_KIND_SNAKECBOX), + // article_kind: None, + // variation: None, + // }, + CharItem { + // Robin Levin Sword + fighter_kind: FIGHTER_KIND_REFLET, + item_kind: Some(ITEM_KIND_THUNDERSWORD), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_TOONLINK, + item_kind: Some(ITEM_KIND_TOONLINKBOMB), + article_kind: None, + variation: None, + }, + // CharItem { + // fighter_kind: FIGHTER_KIND_WARIO, + // item_kind: Some(ITEM_KIND_WARIOBIKE), + // // Pretty sure these other ones are just the bike parts + // // ITEM_KIND_WARIOBIKEA, + // // ITEM_KIND_WARIOBIKEB, + // // ITEM_KIND_WARIOBIKEC, + // // ITEM_KIND_WARIOBIKED, + // // ITEM_KIND_WARIOBIKEE, + // article_kind: None, + // variation: None, + // }, + CharItem { + // Villager Wood Chip + fighter_kind: FIGHTER_KIND_MURABITO, + item_kind: Some(ITEM_KIND_WOOD), + article_kind: None, + variation: None, + }, + CharItem { + fighter_kind: FIGHTER_KIND_YOUNGLINK, + item_kind: Some(ITEM_KIND_YOUNGLINKBOMB), + article_kind: None, + variation: None, + }, +]; + +pub static mut TURNIP_CHOSEN: Option = None; +pub static mut TARGET_PLAYER: Option<*mut BattleObjectModuleAccessor> = None; + +unsafe fn apply_single_item(player_fighter_kind: i32, item: &CharItem) { + let player_module_accessor = get_module_accessor(FighterId::Player); + let cpu_module_accessor = get_module_accessor(FighterId::CPU); + // Now we make sure the module_accessor we use to generate the item/article is the correct character + let generator_module_accessor = if item.fighter_kind == player_fighter_kind { + player_module_accessor + } else { + cpu_module_accessor + }; + let variation = item.variation.as_ref().map(|v| **v).unwrap_or(0); + item.item_kind.as_ref().map(|item_kind| { + let item_kind = **item_kind; + // For Link, use special article generation to link the bomb for detonation + if item_kind == *ITEM_KIND_LINKBOMB { + ArticleModule::generate_article_have_item( + generator_module_accessor, + *FIGHTER_LINK_GENERATE_ARTICLE_LINKBOMB, + *FIGHTER_HAVE_ITEM_WORK_MAIN, + smash::phx::Hash40::new("invalid"), + ); + if player_fighter_kind != *FIGHTER_KIND_LINK { + ItemModule::drop_item(cpu_module_accessor, 0.0, 0.0, 0); + //ItemModule::eject_have_item(cpu_module_accessor, 0, false, false); + let item_mgr = *(ITEM_MANAGER_ADDR as *mut *mut app::ItemManager); + let item_ptr = ItemManager::get_active_item(item_mgr, 0); + ItemModule::have_item_instance( + player_module_accessor, + item_ptr as *mut smash::app::Item, + 0, + false, + false, + false, + false, + ); + } + } else { + ItemModule::have_item( + player_module_accessor, + ItemKind(item_kind), + variation, + 0, + false, + false, + ); + } + }); + + item.article_kind.as_ref().map(|article_kind| { + TURNIP_CHOSEN = if [*ITEM_VARIATION_PEACHDAIKON_8, *ITEM_VARIATION_DAISYDAIKON_8] + .contains(&variation) + { + Some(8) + } else if [*ITEM_VARIATION_PEACHDAIKON_7, *ITEM_VARIATION_DAISYDAIKON_7] + .contains(&variation) + { + Some(7) + } else if [*ITEM_VARIATION_PEACHDAIKON_6, *ITEM_VARIATION_DAISYDAIKON_6] + .contains(&variation) + { + Some(6) + } else if [*ITEM_VARIATION_PEACHDAIKON_1, *ITEM_VARIATION_DAISYDAIKON_1] + .contains(&variation) + { + Some(1) + } else { + None + }; + + let article_kind = **article_kind; + if article_kind == FIGHTER_DIDDY_GENERATE_ARTICLE_ITEM_BANANA { + ArticleModule::generate_article_have_item( + generator_module_accessor, + *FIGHTER_DIDDY_GENERATE_ARTICLE_ITEM_BANANA, + *FIGHTER_HAVE_ITEM_WORK_MAIN, + smash::phx::Hash40::new("invalid"), + ); + WorkModule::on_flag( + generator_module_accessor, + *FIGHTER_DIDDY_STATUS_SPECIAL_LW_FLAG_ITEM_THROW, + ); + ArticleModule::shoot( + generator_module_accessor, + *FIGHTER_DIDDY_GENERATE_ARTICLE_ITEM_BANANA, + ArticleOperationTarget(*ARTICLE_OPE_TARGET_ALL), + false, + ); + // Grab item from the middle of the stage where it gets shot + let item_mgr = *(ITEM_MANAGER_ADDR as *mut *mut app::ItemManager); + let item = ItemManager::get_active_item(item_mgr, 0); + ItemModule::have_item_instance( + player_module_accessor, + item as *mut Item, + 0, + false, + false, + false, + false, + ); + } else { + TARGET_PLAYER = Some(player_module_accessor); // set so we generate CPU article on the player (in dittos, items always belong to player, even if cpu item is chosen) + ArticleModule::generate_article( + generator_module_accessor, // we want CPU's article + article_kind, + false, + 0, + ); + TARGET_PLAYER = None; + } + TURNIP_CHOSEN = None; + }); +} + +pub unsafe fn apply_item(character_item: CharacterItem) { + let player_module_accessor = get_module_accessor(FighterId::Player); + let cpu_module_accessor = get_module_accessor(FighterId::CPU); + let player_fighter_kind = app::utility::get_kind(&mut *player_module_accessor); + let cpu_fighter_kind = app::utility::get_kind(&mut *cpu_module_accessor); + let character_item_num = character_item.as_idx(); + let (item_fighter_kind, variation_idx) = + if character_item_num <= CharacterItem::PlayerVariation8.as_idx() { + ( + player_fighter_kind, + (character_item_num - CharacterItem::PlayerVariation1.as_idx()) as usize, + ) + } else { + ( + cpu_fighter_kind, + (character_item_num - CharacterItem::CpuVariation1.as_idx()) as usize, + ) + }; + ALL_CHAR_ITEMS + .iter() + .filter(|item| item_fighter_kind == item.fighter_kind) + .nth(variation_idx) + .map(|item| apply_single_item(player_fighter_kind, item)); + mash::clear_queue(); +} + +macro_rules! daikon_replace { + ($caps_char: ident, $char:ident, $num:literal) => { + paste::paste! { + extern "C" { + #[link_name = "\u{1}_ZN3app11" $char "daikon31" $caps_char "_" $caps_char "DAIKON_DAIKON_" $num "_PROBEv"] + pub fn [<$char daikon_ $num _prob>]() -> f32; + } + + #[skyline::hook(replace = [<$char daikon_ $num _prob>])] + pub unsafe fn []() -> f32 { + let orig = original!()(); + if is_training_mode() { + if TURNIP_CHOSEN == Some($num) { + return 58.0; + } else if TURNIP_CHOSEN != None { + return 0.0; + } + } + + orig + } + } + }; +} + +daikon_replace!(PEACH, peach, 8); +daikon_replace!(PEACH, peach, 7); +daikon_replace!(PEACH, peach, 6); +daikon_replace!(PEACH, peach, 5); +daikon_replace!(PEACH, peach, 4); +daikon_replace!(PEACH, peach, 3); +daikon_replace!(PEACH, peach, 2); +daikon_replace!(PEACH, peach, 1); +daikon_replace!(DAISY, daisy, 8); +daikon_replace!(DAISY, daisy, 7); +daikon_replace!(DAISY, daisy, 6); +daikon_replace!(DAISY, daisy, 5); +daikon_replace!(DAISY, daisy, 4); +daikon_replace!(DAISY, daisy, 3); +daikon_replace!(DAISY, daisy, 2); +daikon_replace!(DAISY, daisy, 1); + +// GenerateArticleForTarget for Peach/Diddy(/Link?) item creation +static GAFT_OFFSET: usize = 0x03d40a0; +#[skyline::hook(offset = GAFT_OFFSET)] +pub unsafe fn handle_generate_article_for_target( + article_module_accessor: *mut app::BattleObjectModuleAccessor, + int_1: i32, + module_accessor: *mut app::BattleObjectModuleAccessor, // this is always 0x0 normally + bool_1: bool, + int_2: i32, +) -> u64 { + // unknown return value, gets cast to an (Article *) + let target_module_accessor = TARGET_PLAYER.unwrap_or(module_accessor); + let ori = original!()( + article_module_accessor, + int_1, + target_module_accessor, + bool_1, + int_2, + ); + return ori; +} + +pub fn init() { + skyline::install_hooks!( + handle_peachdaikon_8_prob, + handle_peachdaikon_7_prob, + handle_peachdaikon_6_prob, + handle_peachdaikon_5_prob, + handle_peachdaikon_4_prob, + handle_peachdaikon_3_prob, + handle_peachdaikon_2_prob, + handle_peachdaikon_1_prob, + handle_daisydaikon_8_prob, + handle_daisydaikon_7_prob, + handle_daisydaikon_6_prob, + handle_daisydaikon_5_prob, + handle_daisydaikon_4_prob, + handle_daisydaikon_3_prob, + handle_daisydaikon_2_prob, + handle_daisydaikon_1_prob, + // Items + handle_generate_article_for_target, + ); +} diff --git a/src/training/character_specific/mod.rs b/src/training/character_specific/mod.rs index 1b85fdb..8054e4b 100644 --- a/src/training/character_specific/mod.rs +++ b/src/training/character_specific/mod.rs @@ -1,6 +1,7 @@ use smash::app::{self}; mod bowser; +pub mod items; pub mod steve; /** diff --git a/src/training/mash.rs b/src/training/mash.rs index 3724169..d530c2d 100644 --- a/src/training/mash.rs +++ b/src/training/mash.rs @@ -87,6 +87,10 @@ pub fn full_reset() { } } +pub fn clear_queue() { + unsafe { QUEUE.clear() } +} + pub fn set_aerial(attack: Action) { unsafe { CURRENT_AERIAL = attack; diff --git a/src/training/mod.rs b/src/training/mod.rs index 23dea7e..3d457e5 100644 --- a/src/training/mod.rs +++ b/src/training/mod.rs @@ -1,5 +1,8 @@ -use crate::common::{is_training_mode, menu, FIGHTER_MANAGER_ADDR, STAGE_MANAGER_ADDR}; +use crate::common::{ + is_training_mode, menu, FIGHTER_MANAGER_ADDR, ITEM_MANAGER_ADDR, STAGE_MANAGER_ADDR, +}; use crate::hitbox_visualizer; +use crate::training::character_specific::items; use skyline::hooks::{getRegionAddress, InlineCtx, Region}; use skyline::nn::hid::*; use skyline::nn::ro::LookupSymbol; @@ -485,6 +488,13 @@ pub fn training_mods() { .as_ptr(), ); + LookupSymbol( + &mut ITEM_MANAGER_ADDR, + "_ZN3lib9SingletonIN3app11ItemManagerEE9instance_E\0" + .as_bytes() + .as_ptr(), + ); + smash::params::add_hook(params_main).unwrap(); } @@ -536,4 +546,5 @@ pub fn training_mods() { throw::init(); menu::init(); buff::init(); + items::init(); } diff --git a/src/training/save_states.rs b/src/training/save_states.rs index 76facb8..e156662 100644 --- a/src/training/save_states.rs +++ b/src/training/save_states.rs @@ -5,14 +5,15 @@ use crate::common::consts::SaveStateMirroring; use crate::common::is_dead; use crate::common::MENU; use crate::training::buff; +use crate::training::character_specific::steve; use crate::training::charge::{self, ChargeState}; +use crate::training::items::apply_item; use crate::training::reset; -use smash::app::{self, lua_bind::*}; +use smash::app::{self, lua_bind::*, Item}; use smash::hash40; use smash::lib::lua_const::*; use smash::phx::{Hash40, Vector3f}; - -use crate::training::character_specific::steve; +use training_mod_consts::CharacterItem; #[derive(PartialEq)] enum SaveState { @@ -75,6 +76,7 @@ macro_rules! default_save_state { }; } +use crate::ITEM_MANAGER_ADDR; use SaveState::*; static mut SAVE_STATE_PLAYER: SavedState = default_save_state!(); @@ -229,6 +231,16 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor) }; PostureModule::set_pos(module_accessor, &pos); + let item_mgr = *(ITEM_MANAGER_ADDR as *mut *mut app::ItemManager); + (0..ItemManager::get_num_of_active_item_all(item_mgr)).for_each(|item_idx| { + let item = ItemManager::get_active_item(item_mgr, item_idx); + if item != 0 { + let item = item as *mut Item; + let item_battle_object_id = + smash::app::lua_bind::Item::get_battle_object_id(item) as u32; + ItemManager::remove_item_from_id(item_mgr, item_battle_object_id); + } + }); MotionAnimcmdModule::set_sleep(module_accessor, true); SoundModule::pause_se_all(module_accessor, true); ControlModule::stop_rumble(module_accessor, true); @@ -319,6 +331,11 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor) // If we're done moving, reset percent, handle charges, and apply buffs if save_state.state == NoAction { set_damage(module_accessor, save_state.percent); + // Set to held item + if !is_cpu && !fighter_is_nana && MENU.character_item != CharacterItem::None { + apply_item(MENU.character_item); + } + // Set the charge of special moves if the fighter matches the kind in the save state if save_state.fighter_kind == fighter_kind { charge::handle_charge(module_accessor, fighter_kind, save_state.charge); diff --git a/training_mod_consts/src/lib.rs b/training_mod_consts/src/lib.rs index b6e904f..b521f9d 100644 --- a/training_mod_consts/src/lib.rs +++ b/training_mod_consts/src/lib.rs @@ -918,6 +918,71 @@ impl ToUrlParam for i32 { } } +/// Item Selections +#[repr(i32)] +#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter, Serialize, Deserialize)] +pub enum CharacterItem { + None = 0, + PlayerVariation1 = 0x1, + PlayerVariation2 = 0x2, + PlayerVariation3 = 0x4, + PlayerVariation4 = 0x8, + PlayerVariation5 = 0x10, + PlayerVariation6 = 0x20, + PlayerVariation7 = 0x40, + PlayerVariation8 = 0x80, + CpuVariation1 = 0x100, + CpuVariation2 = 0x200, + CpuVariation3 = 0x400, + CpuVariation4 = 0x800, + CpuVariation5 = 0x1000, + CpuVariation6 = 0x2000, + CpuVariation7 = 0x4000, + CpuVariation8 = 0x8000, +} + +impl CharacterItem { + pub fn as_idx(self) -> u32 { + log_2(self as i32 as u32) + } + + pub fn as_str(self) -> Option<&'static str> { + Some(match self { + CharacterItem::PlayerVariation1 => "Player 1st Var.", + CharacterItem::PlayerVariation2 => "Player 2nd Var.", + CharacterItem::PlayerVariation3 => "Player 3rd Var.", + CharacterItem::PlayerVariation4 => "Player 4th Var.", + CharacterItem::PlayerVariation5 => "Player 5th Var.", + CharacterItem::PlayerVariation6 => "Player 6th Var.", + CharacterItem::PlayerVariation7 => "Player 7th Var.", + CharacterItem::PlayerVariation8 => "Player 8th Var.", + CharacterItem::CpuVariation1 => "CPU 1st Var.", + CharacterItem::CpuVariation2 => "CPU 2nd Var.", + CharacterItem::CpuVariation3 => "CPU 3rd Var.", + CharacterItem::CpuVariation4 => "CPU 4th Var.", + CharacterItem::CpuVariation5 => "CPU 5th Var.", + CharacterItem::CpuVariation6 => "CPU 6th Var.", + CharacterItem::CpuVariation7 => "CPU 7th Var.", + CharacterItem::CpuVariation8 => "CPU 8th Var.", + _ => "None", + }) + } + + pub fn to_url_param(&self) -> String { + (*self as i32).to_string() + } +} + +impl ToggleTrait for CharacterItem { + fn to_toggle_strs() -> Vec<&'static str> { + CharacterItem::iter().map(|i| i.as_str().unwrap_or("")).collect() + } + + fn to_toggle_vals() -> Vec { + CharacterItem::iter().map(|i| i as usize).collect() + } +} + // Macro to build the url parameter string macro_rules! url_params { ( @@ -992,6 +1057,7 @@ url_params! { pub throw_delay: MedDelay, pub pummel_delay: MedDelay, pub buff_state: BuffOption, + pub character_item: CharacterItem, pub quick_menu: OnOff, } } @@ -1055,6 +1121,7 @@ impl TrainingModpackMenu { throw_delay = MedDelay::from_bits(val), pummel_delay = MedDelay::from_bits(val), buff_state = BuffOption::from_bits(val), + character_item = num::FromPrimitive::from_u32(val), quick_menu = OnOff::from_val(val), ); } @@ -1120,6 +1187,7 @@ pub static DEFAULTS_MENU: TrainingModpackMenu = TrainingModpackMenu { throw_delay: MedDelay::empty(), pummel_delay: MedDelay::empty(), buff_state: BuffOption::empty(), + character_item: CharacterItem::None, quick_menu: OnOff::Off, }; @@ -1420,9 +1488,9 @@ pub unsafe fn get_menu() -> UiMenu<'static> { false, // TODO: Should this be true? ); defensive_tab.add_submenu_with_toggles::( - "Defensive Toggles", + "Escape Toggles", "defensive_state", - "Defensive Options: Actions to take after a ledge option, tech option, or mistech option", + "Escape Options: Actions to take after a ledge option, tech option, or mistech option", false, ); defensive_tab.add_submenu_with_toggles::( @@ -1431,6 +1499,12 @@ pub unsafe fn get_menu() -> UiMenu<'static> { "Buff Options: Buff(s) to be applied to respective character when loading save states", false, ); + defensive_tab.add_submenu_with_toggles::( + "Character Item", + "character_item", + "Character Item: CPU/Player item to hold when loading a save state", + true + ); overall_menu.tabs.push(defensive_tab); let mut misc_tab = Tab { @@ -1457,7 +1531,7 @@ pub unsafe fn get_menu() -> UiMenu<'static> { true, ); misc_tab.add_submenu_with_toggles::( - "Autoload Save States", + "Save States Autoload", "save_state_autoload", "Save States Autoload: Load save state when any fighter dies", true,