diff --git a/generated/json_data/qol.jsonc b/generated/json_data/qol.jsonc index d5d6b82a..94978935 100644 --- a/generated/json_data/qol.jsonc +++ b/generated/json_data/qol.jsonc @@ -5909,6 +5909,21 @@ "fadeOutTime": 1.5, "audioFileName": "/audio/ice_chapelL.dsp|/audio/ice_chapelR.dsp" } + ], + // Invisible cutscene black bars fix + "cameraFilterKeyframes": [ + { + "id": 524657, // [CameraFilterKeyframe] Camera Filter Keyframe WideScreen + "color": [0, 0, 0, 1], + "filterType": "Multiply", + "filterShape": "CinemaBars" + }, + { + "id": 524816, // [CameraFilterKeyframe] Camera Filter Keyframe + "color": [0, 0, 0, 1], + "filterType": "Multiply", + "filterShape": "CinemaBars" + } ] }, "Control Tower": { diff --git a/generated/json_data/skippable_cutscenes.jsonc b/generated/json_data/skippable_cutscenes.jsonc index 50a5b6ce..b6ea6ef8 100644 --- a/generated/json_data/skippable_cutscenes.jsonc +++ b/generated/json_data/skippable_cutscenes.jsonc @@ -311,6 +311,14 @@ "id": 74, // [SpecialFunction] SpecialFunction Cinematic Skip (Intro Closing) "type": "CinematicSkip" } + ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } ] }, "Map Facility": { @@ -8942,6 +8950,14 @@ "layer": 1, "startImmediately": true } + ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } ] }, "Sun Tower Access": { @@ -10078,6 +10094,14 @@ "id": 1572882, // [SpecialFunction] Arrival Cinematic Skip "type": "CinematicSkip" } + ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } ] }, "Transport to Tallon Overworld East": { @@ -10362,6 +10386,14 @@ "id": 4063244, // [SpecialFunction] SpecialFunction Cinematic Skip "type": "CinematicSkip" } + ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } ] }, "Transport to Tallon Overworld North": { @@ -10640,6 +10672,14 @@ "id": 11, // [SpecialFunction] Arrival Cinematic Skip "type": "CinematicSkip" } + ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } ] }, "Transport to Tallon Overworld South": { @@ -10906,6 +10946,14 @@ "id": 4128779, // [SpecialFunction] Arrival Cinematic Skip "type": "CinematicSkip" } + ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } ] }, "Vault": { @@ -16671,6 +16719,14 @@ "id": 4, // [SpecialFunction] Departure Cinematic Skip "type": "CinematicSkip" } + ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } ] } } @@ -17710,6 +17766,14 @@ "id": 1048597, // [SpecialFunction] Arriving Cutscene Skip "type": "CinematicSkip" } + ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } ] }, "Biohazard Containment": { @@ -19282,6 +19346,14 @@ "id": 1441797, // [SpecialFunction] Departure Cinematic Skip "type": "CinematicSkip" } + ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } ] }, "Transport to Chozo Ruins South": { @@ -19542,6 +19614,14 @@ "id": 2686979, // [SpecialFunction] Departure Cinematic Skip "type": "CinematicSkip" } + ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } ] }, "Transport to Chozo Ruins West": { @@ -19802,6 +19882,14 @@ "id": 917569, // [SpecialFunction] Departure Cinematic Skip "type": "CinematicSkip" } + ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } ] }, "Transport to Magmoor Caverns East": { @@ -20062,6 +20150,14 @@ "id": 1507343, // [SpecialFunction] Arrival Cinematic Skip "type": "CinematicSkip" } + ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } ] }, "Transport to Phazon Mines East": { @@ -20328,6 +20424,14 @@ "id": 2818117, // [SpecialFunction] Arrival Cinematic Skip "type": "CinematicSkip" } + ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } ] } } @@ -24804,6 +24908,14 @@ "id": 1638480, // [SpecialFunction] Departure Cinematic Skip "type": "CinematicSkip" } + ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } ] }, "Transport to Tallon Overworld South": { @@ -25026,6 +25138,14 @@ "shotDuration": 7.0 } ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } + ], "spawnPoints": [ { "id": 5555555, // [SpawnPoint] Morph @@ -25996,6 +26116,14 @@ "shotDuration": 7.0 } ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } + ], "spawnPoints": [ { "id": 5555555, // [SpawnPoint] Morph @@ -26225,6 +26353,14 @@ "time": 0.02 } ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } + ], "specialFunctions": [ { "id": 1703939, // [SpecialFunction] Departure Cinematic Skip @@ -26461,6 +26597,14 @@ "shotDuration": 7.0 } ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } + ], "spawnPoints": [ { "id": 5555555, // [SpawnPoint] Morph @@ -26715,6 +26859,14 @@ "shotDuration": 7.0 } ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } + ], "spawnPoints": [ { "id": 5555555, // [SpawnPoint] Morph @@ -26975,6 +27127,14 @@ "shotDuration": 7.0 } ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } + ], "spawnPoints": [ { "id": 5555555, // [SpawnPoint] Morph @@ -27176,6 +27336,14 @@ "shotDuration": 7.0 } ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } + ], "timers": [ { "id": 1572949, // [Timer] Cutscene Skip 2 Frame Delay @@ -28556,6 +28724,14 @@ "shotDuration": 7.0 } ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } + ], "relays": [ { "id": 720904 // [Relay] END_INVISOCAM @@ -28642,6 +28818,14 @@ "shotDuration": 7.0 } ], + "cameraFilterKeyframes": [ + { + "id": 42069, // [CameraFilterKeyframe] Camera Filter Keyframe Hold Black + "filterShape": "Fullscreen", + "filterType": "Multiply", + "fadeOutTime": 0.5 + } + ], "specialFunctions": [ { "id": 2, // [SpecialFunction] End this Beatch diff --git a/schema/randomprime.schema.json b/schema/randomprime.schema.json index 2e608bd4..2ab99768 100644 --- a/schema/randomprime.schema.json +++ b/schema/randomprime.schema.json @@ -4829,6 +4829,106 @@ ], "additionalProperties": false } + }, + "cameraFilterKeyframes": { + "description": "Add CameraFilterKeyframe objects in this room. They can display certain filters on the screen ranging from black bars seen in cutscenes to screen flashes of any color. `overlay_texture` feature is currently incomplete.", + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "$ref": "#/$defs/addModifyId" + }, + "layer": { + "$ref": "#/$defs/addModifyLayer" + }, + "active": { + "description": "Default active state of the ControllerAction object.", + "type": "boolean", + "default": true + }, + "filterType": { + "description": "Specify the desired type of filter.", + "type": "string", + "enum": [ + "Passthrough", + "Multiply", + "Invert", + "Add", + "Subtract", + "Blend", + "Widescreen", + "SceneAdd", + "NoColor", + "InvDstMultiply" + ], + "default": "Multiply" + }, + "filterShape": { + "description": "Specify the desired shape of the filter.", + "type": "string", + "enum": [ + "Fullscreen", + "FullscreenHalvesLeftRight", + "FullscreenHalvesTopBottom", + "FullscreenQuarters", + "CinemaBars", + "ScanLinesEven", + "ScanLinesOdd", + "RandomStatic", + "CookieCutterDepthRandomStatic" + ], + "default": "CinemaBars" + }, + "filterIndex": { + "description": "The index of the filter. The highest index will be the one to be drawn on top of the other filters with a lower index number.", + "type": "integer", + "default": 0, + "minimum": 0 + }, + "filterGroup": { + "description": "It's use is currently unknown.", + "type": "integer", + "default": 0, + "minimum": 0 + }, + "color": { + "description": "The color of the filter.", + "type": "array", + "items": { + "type": "number", + "minimum": 0.0, + "maximum": 1.0 + }, + "minItems": 4, + "maxItems": 4, + "default": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + "fadeInTime": { + "description": "Specify the speed in which the filter will fade in.", + "type": "number", + "default": 0.0, + "minimum": 0.0 + }, + "fadeOutTime": { + "description": "Specify the speed in which the filter will fade out.", + "type": "number", + "default": 0.0, + "minimum": 0.0 + } + }, + "required": [ + "id", + "filterType", + "filterShape" + ], + "additionalProperties": false + } } }, "additionalProperties": false diff --git a/src/add_modify_obj_patches.rs b/src/add_modify_obj_patches.rs index 4d2cbd71..792f2654 100644 --- a/src/add_modify_obj_patches.rs +++ b/src/add_modify_obj_patches.rs @@ -13,7 +13,7 @@ use crate::{ HudmemoConfig, LockOnPoint, PlatformConfig, PlatformType, PlayerActorConfig, PlayerHintConfig, RelayConfig, SpawnPointConfig, SpecialFunctionConfig, StreamedAudioConfig, SwitchConfig, TimerConfig, TriggerConfig, WaterConfig, WaypointConfig, - WorldLightFaderConfig, + WorldLightFaderConfig, CameraFilterKeyframeConfig, }, patcher::PatcherState, patches::{string_to_cstr, WaterType}, @@ -1855,6 +1855,69 @@ pub fn patch_add_camera_waypoint( ); } +pub fn patch_add_camera_filter_keyframe<'r>( + _ps: &mut PatcherState, + area: &mut mlvl_wrapper::MlvlArea, + config: CameraFilterKeyframeConfig, +) -> Result<(), String> { + macro_rules! new { + () => { + structs::CameraFilterKeyframe { + name: b"my filter\0".as_cstr(), + active: config.active.unwrap_or(true) as u8, + filter_type: config.filter_type as u32, + filter_shape: config.filter_shape as u32, + filter_index: config.filter_index.unwrap_or(0) as u32, + filter_group: config.filter_group.unwrap_or(0) as u32, + color: config.color.unwrap_or([0.0, 0.0, 0.0, 1.0]).into(), + fade_in_time: config.fade_in_time.unwrap_or(0.0) as f32, + fade_out_time: config.fade_out_time.unwrap_or(0.0) as f32, + overlay_texture: config.overlay_texture.unwrap_or(0xFFFFFFFF) as u32, + } + }; + } + + macro_rules! update { + ($obj:expr) => { + let property_data = $obj.property_data.as_camera_filter_keyframe_mut().unwrap(); + + property_data.filter_type = config.filter_type as u32; + property_data.filter_shape = config.filter_shape as u32; + + if let Some(active) = config.active { + property_data.active = active as u8 + } + if let Some(filter_index) = config.filter_index { + property_data.filter_index = filter_index as u32 + } + if let Some(filter_group) = config.filter_group { + property_data.filter_group = filter_group as u32 + } + if let Some(color) = config.color { + property_data.color = color.into() + } + if let Some(fade_in_time) = config.fade_in_time { + property_data.fade_in_time = fade_in_time as f32 + } + if let Some(fade_out_time) = config.fade_out_time { + property_data.fade_out_time = fade_out_time as f32 + } + if let Some(overlay_texture) = config.overlay_texture { + property_data.overlay_texture = overlay_texture as u32 + } + }; + } + + add_edit_obj_helper!( + area, + Some(config.id), + config.layer, + CameraFilterKeyframe, + new, + update + ); +} + pub fn patch_add_platform<'r>( _ps: &mut PatcherState, area: &mut mlvl_wrapper::MlvlArea<'r, '_, '_, '_>, diff --git a/src/patch_config.rs b/src/patch_config.rs index 56513ff5..e2f23800 100644 --- a/src/patch_config.rs +++ b/src/patch_config.rs @@ -779,6 +779,53 @@ pub struct CameraWaypointConfig { pub unknown: Option, } +#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq)] +#[serde(deny_unknown_fields)] +pub enum FilterType { + Passthrough = 0, + Multiply, + Invert, + Add, + Subtract, + Blend, + Widescreen, + SceneAdd, + NoColor, + InvDstMultiply, +} + +#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq)] +#[serde(deny_unknown_fields)] +pub enum FilterShape { + Fullscreen = 0, + FullscreenHalvesLeftRight, + FullscreenHalvesTopBottom, + FullscreenQuarters, + CinemaBars, + ScanLinesEven, + ScanLinesOdd, + RandomStatic, + CookieCutterDepthRandomStatic, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct CameraFilterKeyframeConfig { + pub id: u32, + pub layer: Option, + pub active: Option, + + pub filter_type: FilterType, + pub filter_shape: FilterShape, + + pub filter_index: Option, + pub filter_group: Option, + pub fade_in_time: Option, + pub fade_out_time: Option, + pub color: Option<[f32; 4]>, + pub overlay_texture: Option, +} + #[allow(non_camel_case_types)] #[derive(Debug, Serialize, Deserialize, Copy, Clone, Eq, PartialEq)] #[repr(u32)] @@ -993,6 +1040,7 @@ pub struct RoomConfig { pub world_light_faders: Option>, pub cameras: Option>, pub camera_waypoints: Option>, + pub camera_filter_keyframes: Option>, // Don't forget to update merge_json when adding here } @@ -1883,6 +1931,7 @@ impl PatchConfigPrivate { extend_option_vec!(world_light_faders, self_room_config, other_room_config); extend_option_vec!(cameras, self_room_config, other_room_config); extend_option_vec!(camera_waypoints, self_room_config, other_room_config); + extend_option_vec!(camera_filter_keyframes, self_room_config, other_room_config); if let Some(other_layers) = &other_room_config.layers { if self_room_config.layers.is_none() { diff --git a/src/patches.rs b/src/patches.rs index 9289d872..9793ea3c 100644 --- a/src/patches.rs +++ b/src/patches.rs @@ -34,7 +34,7 @@ use crate::{ }, dol_patcher::DolPatcher, door_meta::{BlastShieldType, DoorType}, - elevators::{is_elevator, is_teleporter, Elevator, SpawnRoom, SpawnRoomData, World}, + elevators::{is_elevator, Elevator, SpawnRoom, SpawnRoomData, World}, extern_assets::ExternPickupModel, gcz_writer::GczWriter, generic_edit::patch_edit_objects, @@ -5354,36 +5354,6 @@ fn patch_post_pq_frigate( Ok(()) } -fn patch_edit_camera_keyframe( - _ps: &mut PatcherState, - area: &mut mlvl_wrapper::MlvlArea, - id: u32, -) -> Result<(), String> { - let layers = area.mrea().scly_section_mut().layers.as_mut_vec(); - for layer in layers.iter_mut() { - let obj = layer - .objects - .iter_mut() - .find(|obj| obj.instance_id & 0x00FFFFFF == id & 0x00FFFFFF); - - if obj.is_none() { - continue; - } - - let camera_keyframe = obj - .unwrap() - .property_data - .as_camera_filter_keyframe_mut() - .unwrap(); - camera_keyframe.filter_type = 1; - camera_keyframe.filter_shape = 4; - camera_keyframe.unknown4 = 2; - camera_keyframe.color = [0.0, 0.0, 0.0, 1.0].into(); - } - - Ok(()) -} - fn patch_sunchamber_cutscene_hack( _ps: &mut PatcherState, area: &mut mlvl_wrapper::MlvlArea, @@ -5588,38 +5558,6 @@ pub fn id_in_use(area: &mut mlvl_wrapper::MlvlArea, id: u32) -> bool { false } -fn patch_add_camera_filter_key_frame( - _ps: &mut PatcherState, - area: &mut mlvl_wrapper::MlvlArea, - id: u32, -) -> Result<(), String> { - if id_in_use(area, id) { - panic!("id 0x{:X} already in use", id); - } - - let scly = area.mrea().scly_section_mut(); - let layer = &mut scly.layers.as_mut_vec()[0]; - layer.objects.as_mut_vec().push(structs::SclyObject { - instance_id: id, - property_data: structs::CameraFilterKeyframe { - name: b"my ckeyframe\0".as_cstr(), - active: 1, - filter_type: 1, - filter_shape: 0, - unknown4: 2, - unknown5: 0, - color: [0.0, 0.0, 0.0, 1.0].into(), - fade_in_time: 0.0, - fade_out_time: 0.5, - overlay_txtr: 0xFFFFFFFF, - } - .into(), - connections: vec![].into(), - }); - - Ok(()) -} - fn patch_add_cutscene_skip_fn( _ps: &mut PatcherState, area: &mut mlvl_wrapper::MlvlArea, @@ -16524,6 +16462,17 @@ fn build_and_run_patches<'r>( } } + if let Some(camera_filter_keyframes) = room.camera_filter_keyframes.as_ref() { + for config in camera_filter_keyframes { + patcher.add_scly_patch( + (pak_name.as_bytes(), room_info.room_id.to_u32()), + move |ps, area| { + patch_add_camera_filter_keyframe(ps, area, config.clone()) + }, + ); + } + } + if room.streamed_audios.is_some() { for config in room.streamed_audios.as_ref().unwrap() { patcher.add_scly_patch( @@ -16579,17 +16528,6 @@ fn build_and_run_patches<'r>( if do_cutscene_skip_patches { /* Some rooms need to be update to play nicely with skippable cutscenes */ match room_info.room_id.to_u32() { - 0x6655F51E => { - // chozo ice temple - for id in [0x80171, 0x80210] { - patcher.add_scly_patch( - (pak_name.as_bytes(), room_info.room_id.to_u32()), - move |ps, area| { - patch_edit_camera_keyframe(ps, area, id) - }, - ); - } - } 0x9A0A03EB => { // sunchamber patcher.add_scly_patch( @@ -16615,23 +16553,6 @@ fn build_and_run_patches<'r>( } _ => {} } - - let room_id = room_info.room_id.to_u32(); - if is_teleporter(room_id) - || [ - 0x90709AAC, // vent shaft - 0x9A0A03EB, - ] - .contains(&room_id) - { - patcher.add_scly_patch( - (pak_name.as_bytes(), room_id), - move |ps: &mut PatcherState, area| { - patch_add_camera_filter_key_frame(ps, area, 0xA455) - }, - ); - - } } if room.actor_keyframes.is_some() { diff --git a/structs/src/scly_props/camera_filter_keyframe.rs b/structs/src/scly_props/camera_filter_keyframe.rs index 129bcd7a..003c4934 100644 --- a/structs/src/scly_props/camera_filter_keyframe.rs +++ b/structs/src/scly_props/camera_filter_keyframe.rs @@ -13,12 +13,12 @@ pub struct CameraFilterKeyframe<'r> { pub active: u8, pub filter_type: u32, pub filter_shape: u32, - pub unknown4: u32, - pub unknown5: u32, + pub filter_index: u32, + pub filter_group: u32, pub color: GenericArray, // RGBA pub fade_in_time: f32, pub fade_out_time: f32, - pub overlay_txtr: u32, + pub overlay_texture: u32 } impl<'r> SclyPropertyData for CameraFilterKeyframe<'r> {