diff --git a/patches/rom_map/Bank 83.txt b/patches/rom_map/Bank 83.txt index 6e349082c..46816f635 100644 --- a/patches/rom_map/Bank 83.txt +++ b/patches/rom_map/Bank 83.txt @@ -1,10 +1,11 @@ 0000 - AD66: Used by vanilla game AD66 - AD72: escape_autosave.asm -B000 - B700: items_disappear.asm +B000 - B700: [FREE] B700 - B800: rng_fix.asm B800 - BA00: Mosaic (Area FX.asm) BA00 - BA15: vanilla_bugfixes.asm -BA15 - BB00: [free] +BA15 - BB00: [FREE] BB00 - BC40: disableable_etanks.asm -BC40 - F000: [free space] +BC40 - E000: [FREE] +E000 - F000: item_dots_disappear.asm F000 - end: Mosaic (FX data) \ No newline at end of file diff --git a/patches/src/item_dots_disappear.asm b/patches/src/item_dots_disappear.asm index 3bbabbf2e..6af682a2b 100644 --- a/patches/src/item_dots_disappear.asm +++ b/patches/src/item_dots_disappear.asm @@ -2,8 +2,8 @@ arch snes.cpu lorom ; Must match locations in patch/map_tiles.rs -!item_list_ptrs = $83B000 -!item_list_sizes = $83B00C +!item_list_ptrs = $83E000 +!item_list_sizes = $83E00C !bank_82_freespace_start = $82FD00 !bank_82_freespace_end = $82FD80 diff --git a/rust/data/presets/doors/Ammo.json b/rust/data/presets/doors/Ammo.json new file mode 100644 index 000000000..4c29b0417 --- /dev/null +++ b/rust/data/presets/doors/Ammo.json @@ -0,0 +1,11 @@ +{ + "preset": "Ammo", + "red_doors_count": 30, + "green_doors_count": 15, + "yellow_doors_count": 10, + "charge_doors_count": 0, + "ice_doors_count": 0, + "wave_doors_count": 0, + "spazer_doors_count": 0, + "plasma_doors_count": 0 +} diff --git a/rust/data/presets/doors/Beam.json b/rust/data/presets/doors/Beam.json new file mode 100644 index 000000000..844e67074 --- /dev/null +++ b/rust/data/presets/doors/Beam.json @@ -0,0 +1,11 @@ +{ + "preset": "Beam", + "red_doors_count": 18, + "green_doors_count": 10, + "yellow_doors_count": 7, + "charge_doors_count": 4, + "ice_doors_count": 4, + "wave_doors_count": 4, + "spazer_doors_count": 4, + "plasma_doors_count": 4 +} diff --git a/rust/data/presets/doors/Blue.json b/rust/data/presets/doors/Blue.json new file mode 100644 index 000000000..71ef781c7 --- /dev/null +++ b/rust/data/presets/doors/Blue.json @@ -0,0 +1,11 @@ +{ + "preset": "Blue", + "red_doors_count": 0, + "green_doors_count": 0, + "yellow_doors_count": 0, + "charge_doors_count": 0, + "ice_doors_count": 0, + "wave_doors_count": 0, + "spazer_doors_count": 0, + "plasma_doors_count": 0 +} diff --git a/rust/data/presets/full-settings/Community Race Season 4.json b/rust/data/presets/full-settings/Community Race Season 4.json index 744d67113..58859f508 100644 --- a/rust/data/presets/full-settings/Community Race Season 4.json +++ b/rust/data/presets/full-settings/Community Race Season 4.json @@ -4638,7 +4638,17 @@ "objective_screen": "Enabled" }, "map_layout": "Standard", - "doors_mode": "Ammo", + "doors_settings": { + "preset": "Ammo", + "red_doors_count": 30, + "green_doors_count": 15, + "yellow_doors_count": 10, + "charge_doors_count": 0, + "ice_doors_count": 0, + "wave_doors_count": 0, + "spazer_doors_count": 0, + "plasma_doors_count": 0 + }, "start_location_settings": { "mode": "Random", "room_id": null, diff --git a/rust/data/presets/full-settings/Default.json b/rust/data/presets/full-settings/Default.json index f2aaab7fa..b7818e54d 100644 --- a/rust/data/presets/full-settings/Default.json +++ b/rust/data/presets/full-settings/Default.json @@ -4638,7 +4638,17 @@ "objective_screen": "Enabled" }, "map_layout": "Standard", - "doors_mode": "Ammo", + "doors_settings": { + "preset": "Ammo", + "red_doors_count": 30, + "green_doors_count": 15, + "yellow_doors_count": 10, + "charge_doors_count": 0, + "ice_doors_count": 0, + "wave_doors_count": 0, + "spazer_doors_count": 0, + "plasma_doors_count": 0 + }, "start_location_settings": { "mode": "Ship" }, diff --git a/rust/maprando-web/src/web/generate.rs b/rust/maprando-web/src/web/generate.rs index c92fed5a6..1930f5d7d 100644 --- a/rust/maprando-web/src/web/generate.rs +++ b/rust/maprando-web/src/web/generate.rs @@ -17,6 +17,7 @@ struct GenerateTemplate<'a> { skill_presets_json: String, item_presets_json: String, qol_presets_json: String, + doors_presets_json: String, objective_presets_json: String, item_priorities: Vec, item_names_multiple: Vec, @@ -117,6 +118,7 @@ async fn generate(app_data: web::Data) -> impl Responder { serde_json::to_string(&app_data.preset_data.item_progression_presets).unwrap(); let qol_presets_json = serde_json::to_string(&app_data.preset_data.quality_of_life_presets).unwrap(); + let doors_presets_json = serde_json::to_string(&app_data.preset_data.doors_presets).unwrap(); let objective_presets_json = serde_json::to_string(&app_data.preset_data.objective_presets).unwrap(); @@ -167,6 +169,7 @@ async fn generate(app_data: web::Data) -> impl Responder { skill_presets_json, item_presets_json, qol_presets_json, + doors_presets_json, objective_presets_json, tech_description: &app_data.game_data.tech_description, tech_dependencies_str: &tech_dependencies_strs, diff --git a/rust/maprando-web/src/web/randomize.rs b/rust/maprando-web/src/web/randomize.rs index 70ccdd541..877292212 100644 --- a/rust/maprando-web/src/web/randomize.rs +++ b/rust/maprando-web/src/web/randomize.rs @@ -54,7 +54,15 @@ struct SeedData { momentum_conservation: bool, fanfares: String, objectives: Vec, - doors: String, + doors_preset: Option, + red_doors_count: i32, + green_doors_count: i32, + yellow_doors_count: i32, + charge_doors_count: i32, + ice_doors_count: i32, + wave_doors_count: i32, + spazer_doors_count: i32, + plasma_doors_count: i32, start_location_mode: String, map_layout: String, save_animals: String, @@ -333,7 +341,15 @@ async fn randomize( .iter() .map(|x| to_variant_name(x).unwrap().to_string()) .collect(), - doors: to_variant_name(&settings.doors_mode).unwrap().to_string(), + doors_preset: settings.doors_settings.preset.clone(), + red_doors_count: settings.doors_settings.red_doors_count, + green_doors_count: settings.doors_settings.green_doors_count, + yellow_doors_count: settings.doors_settings.yellow_doors_count, + charge_doors_count: settings.doors_settings.charge_doors_count, + ice_doors_count: settings.doors_settings.ice_doors_count, + wave_doors_count: settings.doors_settings.wave_doors_count, + spazer_doors_count: settings.doors_settings.spazer_doors_count, + plasma_doors_count: settings.doors_settings.plasma_doors_count, start_location_mode: if settings.start_location_settings.mode == StartLocationMode::Custom { output.randomization.start_location.name.clone() } else { diff --git a/rust/maprando-web/src/web/randomize/helpers.rs b/rust/maprando-web/src/web/randomize/helpers.rs index a590fb1dd..0ec7b32db 100644 --- a/rust/maprando-web/src/web/randomize/helpers.rs +++ b/rust/maprando-web/src/web/randomize/helpers.rs @@ -60,7 +60,15 @@ pub struct SeedHeaderTemplate<'a> { fanfares: String, etank_refill: String, disableable_etanks: String, - doors: String, + doors_preset: String, + red_doors_count: i32, + green_doors_count: i32, + yellow_doors_count: i32, + charge_doors_count: i32, + ice_doors_count: i32, + wave_doors_count: i32, + spazer_doors_count: i32, + plasma_doors_count: i32, start_location_mode: String, map_layout: String, save_animals: String, @@ -466,7 +474,18 @@ pub fn render_seed( DisableETankSetting::Unrestricted => "Unrestricted", } .to_string(), - doors: seed_data.doors.clone(), + doors_preset: seed_data + .doors_preset + .clone() + .unwrap_or("Custom".to_string()), + red_doors_count: seed_data.red_doors_count, + green_doors_count: seed_data.green_doors_count, + yellow_doors_count: seed_data.yellow_doors_count, + charge_doors_count: seed_data.charge_doors_count, + ice_doors_count: seed_data.ice_doors_count, + wave_doors_count: seed_data.wave_doors_count, + spazer_doors_count: seed_data.spazer_doors_count, + plasma_doors_count: seed_data.plasma_doors_count, start_location_mode: seed_data.start_location_mode.clone(), map_layout: seed_data.map_layout.clone(), save_animals: seed_data.save_animals.clone(), diff --git a/rust/maprando-web/templates/generate/doors.html b/rust/maprando-web/templates/generate/doors.html new file mode 100644 index 000000000..9be5e08e5 --- /dev/null +++ b/rust/maprando-web/templates/generate/doors.html @@ -0,0 +1,85 @@ + + diff --git a/rust/maprando-web/templates/generate/help/doors.html b/rust/maprando-web/templates/generate/help/doors.html index 7b7328739..548454709 100644 --- a/rust/maprando-web/templates/generate/help/doors.html +++ b/rust/maprando-web/templates/generate/help/doors.html @@ -1,9 +1,9 @@ - - + +
+
+ Doors details +
+
+ {% include "doors_details.html" %} +
+
diff --git a/rust/maprando/src/patch/map_tiles.rs b/rust/maprando/src/patch/map_tiles.rs index 54e25c7c2..0bb977eab 100644 --- a/rust/maprando/src/patch/map_tiles.rs +++ b/rust/maprando/src/patch/map_tiles.rs @@ -2374,7 +2374,7 @@ impl<'a> MapPatcher<'a> { area_data: &[Vec<(ItemIdx, RoomId, MapTile)>], ) -> Result<()> { // Write per-area item listings, to be used by the patch `item_dots_disappear.asm`. - let base_ptr = 0x83B000; + let base_ptr = 0x83E000; let mut data_ptr = base_ptr + 24; for (area_idx, data) in area_data.iter().enumerate() { self.rom.write_u16( @@ -2400,7 +2400,7 @@ impl<'a> MapPatcher<'a> { } assert_eq!(data_ptr, data_start + 6 * data.len()); } - assert!(data_ptr <= 0x83B600); + assert!(data_ptr <= 0x83F000); Ok(()) } diff --git a/rust/maprando/src/preset.rs b/rust/maprando/src/preset.rs index 76a77442c..22eaca5f1 100644 --- a/rust/maprando/src/preset.rs +++ b/rust/maprando/src/preset.rs @@ -9,8 +9,8 @@ use serde::{Deserialize, Serialize}; use crate::{ randomize::DifficultyConfig, settings::{ - ItemProgressionSettings, ObjectiveSettings, QualityOfLifeSettings, RandomizerSettings, - SkillAssumptionSettings, + DoorsSettings, ItemProgressionSettings, ObjectiveSettings, QualityOfLifeSettings, + RandomizerSettings, SkillAssumptionSettings, }, }; @@ -46,6 +46,7 @@ pub struct PresetData { pub full_presets: Vec, pub default_preset: RandomizerSettings, pub logic_page_presets: Vec, + pub doors_presets: Vec, } fn get_tech_by_difficulty( @@ -213,6 +214,19 @@ impl PresetData { objective_presets.push(preset); } + let doors_preset_names = ["Blue", "Ammo", "Beam"]; + let doors_preset_path = presets_path.join("doors"); + let mut doors_presets: Vec = vec![]; + for name in doors_preset_names { + let path = doors_preset_path.join(format!("{name}.json")); + let preset_str = std::fs::read_to_string(path.clone()) + .context(format!("reading from {}", path.display()))?; + let preset: DoorsSettings = + serde_json::from_str(&preset_str).context(format!("parsing {}", path.display()))?; + assert!(preset.preset == Some(name.to_string())); + doors_presets.push(preset); + } + let full_preset_names = ["Default", "Community Race Season 4"]; let full_preset_path = presets_path.join("full-settings"); let mut full_presets: Vec = vec![]; @@ -252,6 +266,7 @@ impl PresetData { default_preset, logic_page_presets, full_presets, + doors_presets, }; Ok(preset_data) } diff --git a/rust/maprando/src/randomize.rs b/rust/maprando/src/randomize.rs index 3e0b7fc93..2936f4f10 100644 --- a/rust/maprando/src/randomize.rs +++ b/rust/maprando/src/randomize.rs @@ -5,7 +5,7 @@ use crate::helpers::get_item_priorities; use crate::patch::NUM_AREAS; use crate::patch::map_tiles::get_objective_tiles; use crate::settings::{ - AreaAssignmentBaseOrder, DoorsMode, FillerItemPriority, ItemCount, ItemPlacementStyle, + AreaAssignmentBaseOrder, FillerItemPriority, ItemCount, ItemPlacementStyle, ItemPriorityStrength, KeyItemPriority, MotherBrainFight, Objective, ObjectiveSetting, ProgressionRate, RandomizerSettings, SaveAnimals, SkillAssumptionSettings, SpeedBooster, StartLocationMode, WallJump, @@ -3210,33 +3210,73 @@ pub fn randomize_doors( let mut used_beam_rooms: HashSet = HashSet::new(); let mut door_types = vec![]; - match settings.doors_mode { - DoorsMode::Blue => {} - DoorsMode::Ammo => { - let red_doors_cnt = 30; - let green_doors_cnt = 15; - let yellow_doors_cnt = 10; - door_types.extend(vec![DoorType::Red; red_doors_cnt]); - door_types.extend(vec![DoorType::Green; green_doors_cnt]); - door_types.extend(vec![DoorType::Yellow; yellow_doors_cnt]); - } - DoorsMode::Beam => { - let red_doors_cnt = 18; - let green_doors_cnt = 10; - let yellow_doors_cnt = 7; - let beam_door_each_cnt = 4; - door_types.extend(vec![DoorType::Red; red_doors_cnt]); - door_types.extend(vec![DoorType::Green; green_doors_cnt]); - door_types.extend(vec![DoorType::Yellow; yellow_doors_cnt]); - door_types.extend(vec![DoorType::Beam(BeamType::Charge); beam_door_each_cnt]); - door_types.extend(vec![DoorType::Beam(BeamType::Ice); beam_door_each_cnt]); - door_types.extend(vec![DoorType::Beam(BeamType::Wave); beam_door_each_cnt]); - door_types.extend(vec![DoorType::Beam(BeamType::Spazer); beam_door_each_cnt]); - door_types.extend(vec![DoorType::Beam(BeamType::Plasma); beam_door_each_cnt]); - } - }; + if settings.doors_settings.red_doors_count > 0 { + door_types.extend(vec![ + DoorType::Red; + settings.doors_settings.red_doors_count as usize + ]); + } + if settings.doors_settings.green_doors_count > 0 { + door_types.extend(vec![ + DoorType::Green; + settings.doors_settings.green_doors_count as usize + ]); + } + if settings.doors_settings.yellow_doors_count > 0 { + door_types.extend(vec![ + DoorType::Yellow; + settings.doors_settings.yellow_doors_count as usize + ]); + } + if settings.doors_settings.charge_doors_count > 0 { + door_types.extend(vec![ + DoorType::Beam(BeamType::Charge); + settings.doors_settings.charge_doors_count as usize + ]); + } + if settings.doors_settings.ice_doors_count > 0 { + door_types.extend(vec![ + DoorType::Beam(BeamType::Ice); + settings.doors_settings.ice_doors_count as usize + ]); + } + if settings.doors_settings.wave_doors_count > 0 { + door_types.extend(vec![ + DoorType::Beam(BeamType::Wave); + settings.doors_settings.wave_doors_count as usize + ]); + } + if settings.doors_settings.spazer_doors_count > 0 { + door_types.extend(vec![ + DoorType::Beam(BeamType::Spazer); + settings.doors_settings.spazer_doors_count as usize + ]); + } + if settings.doors_settings.plasma_doors_count > 0 { + door_types.extend(vec![ + DoorType::Beam(BeamType::Plasma); + settings.doors_settings.plasma_doors_count as usize + ]); + } let walls = get_walls(map, game_data); let door_conns = get_randomizable_door_connections(game_data, map, &walls, objectives); + + // The ability to support more doors is limited by the space for dynamic tiles in bank E4. + // That data could possibly be moved out to another bank to free up more space. + // For now we limit the amount of ammo/beam doors to 120. + let door_count_limit = door_conns.len().min(120); + if door_types.len() > door_count_limit { + let mut keep_doors_idx = + rand::seq::index::sample(&mut rng, door_types.len(), door_count_limit).into_vec(); + keep_doors_idx.sort(); + let mut keep_doors = Vec::with_capacity(door_count_limit); + for idx in keep_doors_idx.into_iter() { + keep_doors.push(door_types[idx]); + } + door_types.clear(); + door_types.extend(keep_doors); + } + let mut locked_doors: Vec = vec![]; let total_cnt = door_types.len(); let idxs = rand::seq::index::sample(&mut rng, door_conns.len(), total_cnt); diff --git a/rust/maprando/src/settings.rs b/rust/maprando/src/settings.rs index 9826b936c..7672bd3d3 100644 --- a/rust/maprando/src/settings.rs +++ b/rust/maprando/src/settings.rs @@ -18,7 +18,7 @@ pub struct RandomizerSettings { pub quality_of_life_settings: QualityOfLifeSettings, pub objective_settings: ObjectiveSettings, pub map_layout: String, - pub doors_mode: DoorsMode, + pub doors_settings: DoorsSettings, pub start_location_settings: StartLocationSettings, pub save_animals: SaveAnimals, pub other_settings: OtherSettings, @@ -26,6 +26,19 @@ pub struct RandomizerSettings { pub debug: bool, } +#[derive(Serialize, Deserialize, Clone, PartialEq)] +pub struct DoorsSettings { + pub preset: Option, + pub red_doors_count: i32, + pub green_doors_count: i32, + pub yellow_doors_count: i32, + pub charge_doors_count: i32, + pub ice_doors_count: i32, + pub wave_doors_count: i32, + pub spazer_doors_count: i32, + pub plasma_doors_count: i32, +} + #[derive(Serialize, Deserialize, Clone, PartialEq)] pub struct SkillAssumptionSettings { pub preset: Option, @@ -919,6 +932,31 @@ fn upgrade_objective_settings( Ok(()) } +fn upgrade_doors_settings( + settings: &mut serde_json::Value, + preset_data: &PresetData, +) -> Result<()> { + let settings_obj = settings + .as_object_mut() + .context("expected settings to be object")?; + + if !settings_obj.contains_key("doors_settings") { + settings_obj.insert( + "doors_settings".to_string(), + serde_json::to_value(preset_data.doors_presets[0].clone()).unwrap(), + ); + } + + if settings_obj.contains_key("doors_mode") { + *settings_obj + .get_mut("doors_settings") + .unwrap() + .get_mut("preset") + .unwrap() = settings_obj["doors_mode"].as_str().unwrap().into(); + } + Ok(()) +} + fn upgrade_other_settings(settings: &mut serde_json::Value) -> Result<()> { let settings_obj = settings .as_object_mut() @@ -989,6 +1027,7 @@ pub fn try_upgrade_settings( upgrade_item_progression_settings(&mut settings)?; upgrade_qol_settings(&mut settings)?; upgrade_map_setting(&mut settings)?; + upgrade_doors_settings(&mut settings, preset_data)?; upgrade_other_settings(&mut settings)?; upgrade_start_location_setings(&mut settings)?; upgrade_animals_setting(&mut settings)?; diff --git a/rust/maprando/tests/logic_scenarios.rs b/rust/maprando/tests/logic_scenarios.rs index 514900699..50b115ff8 100644 --- a/rust/maprando/tests/logic_scenarios.rs +++ b/rust/maprando/tests/logic_scenarios.rs @@ -5,8 +5,8 @@ use hashbrown::HashMap; use maprando::{ randomize::{DifficultyConfig, LockedDoor, Preprocessor, make_locked_door_data}, settings::{ - DisableETankSetting, InitialMapRevealSettings, ItemProgressionSettings, Objective, - ObjectiveSettings, OtherSettings, QualityOfLifeSettings, RandomizerSettings, + DisableETankSetting, DoorsSettings, InitialMapRevealSettings, ItemProgressionSettings, + Objective, ObjectiveSettings, OtherSettings, QualityOfLifeSettings, RandomizerSettings, SkillAssumptionSettings, StartLocationSettings, }, traverse::{LockedDoorData, Traverser}, @@ -271,7 +271,17 @@ fn get_settings(scenario: &Scenario) -> Result { objective_screen: maprando::settings::ObjectiveScreen::Disabled, }, map_layout: String::new(), - doors_mode: maprando::settings::DoorsMode::Blue, + doors_settings: DoorsSettings { + preset: Some("Blue".to_string()), + red_doors_count: 0, + green_doors_count: 0, + yellow_doors_count: 0, + charge_doors_count: 0, + ice_doors_count: 0, + wave_doors_count: 0, + spazer_doors_count: 0, + plasma_doors_count: 0, + }, start_location_settings: StartLocationSettings { mode: maprando::settings::StartLocationMode::Ship, room_id: None,