1
0
Fork 0
mirror of https://github.com/jugeeya/UltimateTrainingModpack.git synced 2024-11-20 00:46:34 +00:00

Save State Slots as Menu Option (#520)

* Initial

* Clippy + Format

* Update save_states.rs

* selected vs random

* fix

* Formats

* Format

* Format

* Updated copy, 2 randomize_slots, 2 save_slot_state

* Update to 5.1, fix random logic

---------

Co-authored-by: Matthew Edell <edell.matthew@gmail.com>
This commit is contained in:
jugeeya 2023-04-14 15:48:22 -07:00 committed by GitHub
parent 188e7decad
commit 1ce2850af3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 95 additions and 114 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "training_modpack" name = "training_modpack"
version = "5.0.0" version = "5.1.0"
authors = ["jugeeya <jugeeya@live.com>"] authors = ["jugeeya <jugeeya@live.com>"]
edition = "2018" edition = "2018"

View file

@ -156,6 +156,8 @@ The timing of the CPU option can be influenced by the following settings:
| Save Damage (Player) | What to use for player's damage on save state load | Default, Save State, Random | | Save Damage (Player) | What to use for player's damage on save state load | Default, Save State, Random |
| Damage Range (Player) | Random percentage for save state load for player | 0 to 150 % | | Damage Range (Player) | Random percentage for save state load for player | 0 to 150 % |
| Enable Save States | Should save states be enabled or disabled | Yes, No | | Enable Save States | Should save states be enabled or disabled | Yes, No |
| Save State Slot | Save and load states from different slots | 1, 2, 3, 4, 5 |
| Save State Random Slot | Load from a random slot | Yes, No |
| Character Item | The item to give to the player's fighter when loading a save state | None, Player item (#1 - #8), CPU item (#1 - #8) | | Character Item | The item to give to the player's fighter when loading a save state | None, Player item (#1 - #8), CPU item (#1 - #8) |
| Buff Options | Buff(s) to be applied to respective character when loading save states | Accelerate, Oomph, Psyche Up, Bounce, Arsene, Deep Breathing, Limit, K.O. Punch, Wing | | Buff Options | Buff(s) to be applied to respective character when loading save states | Accelerate, Oomph, Psyche Up, Bounce, Arsene, Deep Breathing, Limit, K.O. Punch, Wing |
@ -165,7 +167,7 @@ The timing of the CPU option can be influenced by the following settings:
At any time in Training Mode, you can press `Shield + Down Taunt` to save the state of training mode. This will save the position, state, and damage of each fighter, which can then be reverted to at any time with `Shield + Up Taunt`. With the mirroring setting, loading the save state will flip the positions, allowing you to practice your skills facing both directions. Use this instead of the built-in training mode reset! At any time in Training Mode, you can press `Shield + Down Taunt` to save the state of training mode. This will save the position, state, and damage of each fighter, which can then be reverted to at any time with `Shield + Up Taunt`. With the mirroring setting, loading the save state will flip the positions, allowing you to practice your skills facing both directions. Use this instead of the built-in training mode reset!
You can switch Save State slots by using `Grab + Left Taunt` to switch to a previous slot and `Grab + Right Taunt` to switch to the next. There are 5 slots you can save, and they are persisted between loads of the game! You can switch Save State slots by using the associated toggle! There are 5 slots you can save, and they are persisted between loads of the game!
The following attributes are saved in the save states: The following attributes are saved in the save states:

View file

@ -40,14 +40,6 @@ static mut BUTTON_COMBO_CONFIG: BtnComboConfig = BtnComboConfig {
hold: vec![], hold: vec![],
press: vec![], press: vec![],
}, },
previous_save_state_slot: BtnList {
hold: vec![],
press: vec![],
},
next_save_state_slot: BtnList {
hold: vec![],
press: vec![],
},
}; };
#[derive(Debug, EnumIter, PartialEq)] #[derive(Debug, EnumIter, PartialEq)]
@ -55,8 +47,6 @@ pub enum ButtonCombo {
OpenMenu, OpenMenu,
SaveState, SaveState,
LoadState, LoadState,
PrevSaveStateSlot,
NextSaveStateSlot,
} }
#[derive(Deserialize, Default)] #[derive(Deserialize, Default)]
@ -70,8 +60,6 @@ struct BtnComboConfig {
open_menu: BtnList, open_menu: BtnList,
save_state: BtnList, save_state: BtnList,
load_state: BtnList, load_state: BtnList,
previous_save_state_slot: BtnList,
next_save_state_slot: BtnList,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -118,14 +106,6 @@ fn save_all_btn_config_from_defaults() {
hold: vec!["SHIELD".to_string()], hold: vec!["SHIELD".to_string()],
press: vec!["UPTAUNT".to_string()], press: vec!["UPTAUNT".to_string()],
}, },
previous_save_state_slot: BtnList {
hold: vec!["GRAB".to_string()],
press: vec!["LEFTTAUNT".to_string()],
},
next_save_state_slot: BtnList {
hold: vec!["GRAB".to_string()],
press: vec!["RIGHTTAUNT".to_string()],
},
}, },
}; };
unsafe { unsafe {
@ -146,13 +126,7 @@ fn save_all_btn_config_from_toml(data: &str) {
fn validate_config(conf: TopLevelBtnComboConfig) -> bool { fn validate_config(conf: TopLevelBtnComboConfig) -> bool {
let conf = conf.button_config; let conf = conf.button_config;
let configs = [ let configs = [conf.open_menu, conf.save_state, conf.load_state];
conf.open_menu,
conf.save_state,
conf.load_state,
conf.previous_save_state_slot,
conf.next_save_state_slot,
];
let bad_keys = configs let bad_keys = configs
.iter() .iter()
.flat_map(|btn_list| { .flat_map(|btn_list| {
@ -196,14 +170,6 @@ unsafe fn get_combo_keys(combo: ButtonCombo) -> (&'static Vec<String>, &'static
&BUTTON_COMBO_CONFIG.load_state.hold, &BUTTON_COMBO_CONFIG.load_state.hold,
&BUTTON_COMBO_CONFIG.load_state.press, &BUTTON_COMBO_CONFIG.load_state.press,
), ),
ButtonCombo::PrevSaveStateSlot => (
&BUTTON_COMBO_CONFIG.previous_save_state_slot.hold,
&BUTTON_COMBO_CONFIG.previous_save_state_slot.press,
),
ButtonCombo::NextSaveStateSlot => (
&BUTTON_COMBO_CONFIG.next_save_state_slot.hold,
&BUTTON_COMBO_CONFIG.next_save_state_slot.press,
),
} }
} }
@ -268,12 +234,4 @@ press=["DOWNTAUNT",]
[button_config.load_state] [button_config.load_state]
hold=["SHIELD",] hold=["SHIELD",]
press=["UPTAUNT",] press=["UPTAUNT",]
[button_config.previous_save_state_slot]
hold=["GRAB",]
press=["LEFTTAUNT",]
[button_config.next_save_state_slot]
hold=["GRAB",]
press=["RIGHTTAUNT",]
"#; "#;

Binary file not shown.

View file

@ -108,7 +108,6 @@ const NUM_SAVE_STATE_SLOTS: usize = 5;
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref SAVE_STATE_SLOTS : Mutex<SaveStateSlots> = Mutex::new(load_from_file()); static ref SAVE_STATE_SLOTS : Mutex<SaveStateSlots> = Mutex::new(load_from_file());
} }
static mut SAVE_STATE_SLOT: usize = 0;
pub fn load_from_file() -> SaveStateSlots { pub fn load_from_file() -> SaveStateSlots {
let defaults = SaveStateSlots { let defaults = SaveStateSlots {
@ -139,25 +138,32 @@ pub unsafe fn save_to_file() {
.expect("Could not write save state information to file"); .expect("Could not write save state information to file");
} }
unsafe fn save_state_player() -> &'static mut SavedState { unsafe fn save_state_player(slot: usize) -> &'static mut SavedState {
&mut (*SAVE_STATE_SLOTS.data_ptr()).player[SAVE_STATE_SLOT] &mut (*SAVE_STATE_SLOTS.data_ptr()).player[slot]
} }
unsafe fn save_state_cpu() -> &'static mut SavedState { unsafe fn save_state_cpu(slot: usize) -> &'static mut SavedState {
&mut (*SAVE_STATE_SLOTS.data_ptr()).cpu[SAVE_STATE_SLOT] &mut (*SAVE_STATE_SLOTS.data_ptr()).cpu[slot]
} }
// MIRROR_STATE == 1 -> Do not mirror // MIRROR_STATE == 1 -> Do not mirror
// MIRROR_STATE == -1 -> Do Mirror // MIRROR_STATE == -1 -> Do Mirror
static mut MIRROR_STATE: f32 = 1.0; static mut MIRROR_STATE: f32 = 1.0;
static mut RANDOM_SLOT: usize = 0;
pub unsafe fn is_killing() -> bool { pub unsafe fn is_killing() -> bool {
(save_state_player().state == KillPlayer || save_state_player().state == WaitForAlive) let selected_slot = MENU.save_state_slot as u32 as usize;
|| (save_state_cpu().state == KillPlayer || save_state_cpu().state == WaitForAlive) (save_state_player(selected_slot).state == KillPlayer
|| save_state_player(selected_slot).state == WaitForAlive)
|| (save_state_cpu(selected_slot).state == KillPlayer
|| save_state_cpu(selected_slot).state == WaitForAlive)
} }
pub unsafe fn is_loading() -> bool { pub unsafe fn is_loading() -> bool {
save_state_player().state != NoAction || save_state_cpu().state != NoAction let selected_slot = MENU.save_state_slot as u32 as usize;
save_state_player(selected_slot).state != NoAction
|| save_state_cpu(selected_slot).state != NoAction
} }
pub unsafe fn should_mirror() -> f32 { pub unsafe fn should_mirror() -> f32 {
@ -331,13 +337,19 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
return; return;
} }
let selected_slot = if MENU.randomize_slots == OnOff::On {
RANDOM_SLOT
} else {
MENU.save_state_slot as u32 as usize
};
let status = StatusModule::status_kind(module_accessor); let status = StatusModule::status_kind(module_accessor);
let is_cpu = WorkModule::get_int(module_accessor, *FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID) let is_cpu = WorkModule::get_int(module_accessor, *FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID)
== FighterId::CPU as i32; == FighterId::CPU as i32;
let save_state = if is_cpu { let save_state = if is_cpu {
save_state_cpu() save_state_cpu(selected_slot)
} else { } else {
save_state_player() save_state_player(selected_slot)
}; };
let fighter_kind = app::utility::get_kind(module_accessor); let fighter_kind = app::utility::get_kind(module_accessor);
@ -354,46 +366,6 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
] ]
.contains(&fighter_kind); .contains(&fighter_kind);
if MENU.save_state_slot_enable == OnOff::On
&& !is_operation_cpu(module_accessor)
&& button_config::combo_passes_exclusive(
module_accessor,
button_config::ButtonCombo::PrevSaveStateSlot,
)
{
SAVE_STATE_SLOT = if SAVE_STATE_SLOT == 0 {
NUM_SAVE_STATE_SLOTS - 1
} else {
SAVE_STATE_SLOT - 1
};
notifications::clear_notifications("Save State");
notifications::notification(
"Save State".to_string(),
format!("Switched to Slot {SAVE_STATE_SLOT}"),
120,
);
return;
}
if MENU.save_state_slot_enable == OnOff::On
&& !is_operation_cpu(module_accessor)
&& button_config::combo_passes_exclusive(
module_accessor,
button_config::ButtonCombo::NextSaveStateSlot,
)
{
SAVE_STATE_SLOT = (SAVE_STATE_SLOT + 1) % NUM_SAVE_STATE_SLOTS;
notifications::clear_notifications("Save State");
notifications::notification(
"Save State".to_string(),
format!("Switched to Slot {SAVE_STATE_SLOT}"),
120,
);
return;
}
// Reset state // Reset state
let autoload_reset = MENU.save_state_autoload == OnOff::On let autoload_reset = MENU.save_state_autoload == OnOff::On
&& save_state.state == NoAction && save_state.state == NoAction
@ -407,8 +379,15 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
} }
if (autoload_reset || triggered_reset) && !fighter_is_nana { if (autoload_reset || triggered_reset) && !fighter_is_nana {
if save_state.state == NoAction { if save_state.state == NoAction {
save_state_player().state = KillPlayer; let slot = if MENU.randomize_slots == OnOff::On {
save_state_cpu().state = KillPlayer; RANDOM_SLOT = get_random_int(NUM_SAVE_STATE_SLOTS as i32) as usize;
RANDOM_SLOT
} else {
selected_slot
};
save_state_player(slot).state = KillPlayer;
save_state_cpu(slot).state = KillPlayer;
} }
MIRROR_STATE = should_mirror(); MIRROR_STATE = should_mirror();
return; return;
@ -612,12 +591,12 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
{ {
// Don't begin saving state if Nana's delayed input is captured // Don't begin saving state if Nana's delayed input is captured
MIRROR_STATE = 1.0; MIRROR_STATE = 1.0;
save_state_player().state = Save; save_state_player(MENU.save_state_slot as u32 as usize).state = Save;
save_state_cpu().state = Save; save_state_cpu(MENU.save_state_slot as u32 as usize).state = Save;
notifications::clear_notifications("Save State"); notifications::clear_notifications("Save State");
notifications::notification( notifications::notification(
"Save State".to_string(), "Save State".to_string(),
format!("Saved Slot {SAVE_STATE_SLOT}"), format!("Saved Slot {}", MENU.save_state_slot.as_str().unwrap()),
120, 120,
); );
} }
@ -666,7 +645,9 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
); );
// If both chars finished saving by now // If both chars finished saving by now
if save_state_player().state != Save && save_state_cpu().state != Save { if save_state_player(selected_slot).state != Save
&& save_state_cpu(selected_slot).state != Save
{
save_to_file(); save_to_file();
} }
} }

View file

@ -48,7 +48,8 @@ pub struct TrainingModpackMenu {
pub save_damage_limits_player: DamagePercent, pub save_damage_limits_player: DamagePercent,
pub save_state_autoload: OnOff, pub save_state_autoload: OnOff,
pub save_state_enable: OnOff, pub save_state_enable: OnOff,
pub save_state_slot_enable: OnOff, pub save_state_slot: SaveStateSlot,
pub randomize_slots: OnOff,
pub save_state_mirroring: SaveStateMirroring, pub save_state_mirroring: SaveStateMirroring,
pub sdi_state: Direction, pub sdi_state: Direction,
pub sdi_strength: SdiFrequency, pub sdi_strength: SdiFrequency,
@ -124,7 +125,8 @@ pub static DEFAULTS_MENU: TrainingModpackMenu = TrainingModpackMenu {
save_damage_limits_player: DamagePercent::default(), save_damage_limits_player: DamagePercent::default(),
save_state_autoload: OnOff::Off, save_state_autoload: OnOff::Off,
save_state_enable: OnOff::On, save_state_enable: OnOff::On,
save_state_slot_enable: OnOff::On, save_state_slot: SaveStateSlot::One,
randomize_slots: OnOff::Off,
save_state_mirroring: SaveStateMirroring::None, save_state_mirroring: SaveStateMirroring::None,
sdi_state: Direction::empty(), sdi_state: Direction::empty(),
sdi_strength: SdiFrequency::None, sdi_strength: SdiFrequency::None,
@ -529,18 +531,19 @@ pub unsafe fn ui_menu(menu: TrainingModpackMenu) -> UiMenu<'static> {
true, true,
&(menu.save_state_enable as u32), &(menu.save_state_enable as u32),
); );
save_state_tab.add_submenu_with_toggles::<OnOff>( save_state_tab.add_submenu_with_toggles::<SaveStateSlot>(
"Save State Slot",
"Enable Slots", "save_state_slot",
"Save State Slot: Save and load states from different slots.",
"save_state_slot_enable",
"Save State Slots: Enable save state slots. Switch to a different slot with Grab+Left or Right Taunt.",
true, true,
&(menu.save_state_slot as u32),
&(menu.save_state_enable as u32), );
save_state_tab.add_submenu_with_toggles::<OnOff>(
"Randomize Slots",
"randomize_slots",
"Randomize Slots: Randomize slot when loading save state.",
true,
&(menu.randomize_slots as u32),
); );
save_state_tab.add_submenu_with_toggles::<CharacterItem>( save_state_tab.add_submenu_with_toggles::<CharacterItem>(
"Character Item", "Character Item",

View file

@ -1105,3 +1105,40 @@ impl SaveDamage {
extra_bitflag_impls! {SaveDamage} extra_bitflag_impls! {SaveDamage}
impl_serde_for_bitflags!(SaveDamage); impl_serde_for_bitflags!(SaveDamage);
/// Save State Slots
#[repr(i32)]
#[derive(
Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter, Serialize_repr, Deserialize_repr,
)]
pub enum SaveStateSlot {
One = 0,
Two = 1,
Three = 2,
Four = 3,
Five = 4,
}
impl SaveStateSlot {
pub fn as_str(self) -> Option<&'static str> {
Some(match self {
SaveStateSlot::One => "1",
SaveStateSlot::Two => "2",
SaveStateSlot::Three => "3",
SaveStateSlot::Four => "4",
SaveStateSlot::Five => "5",
})
}
}
impl ToggleTrait for SaveStateSlot {
fn to_toggle_strs() -> Vec<&'static str> {
SaveStateSlot::iter()
.map(|i| i.as_str().unwrap_or(""))
.collect()
}
fn to_toggle_vals() -> Vec<u32> {
SaveStateSlot::iter().map(|i| i as u32).collect()
}
}