From cd0af9359dca5237f6f2d558e16b5984f982210a Mon Sep 17 00:00:00 2001 From: UltiNaruto Date: Sun, 4 Aug 2024 14:00:18 +0200 Subject: [PATCH] Custom item support Required mains supported for Missiles/PB Unlimited Missiles/PBs are supported now Spring Ball has its own item now --- ppcasm/ppcasm_macro/src/lib.rs | 4 + schema/randomprime.schema.json | 31 +- src/custom_assets.rs | 18 +- src/patch_config.rs | 11 +- src/patches.rs | 508 +++++++++++++++++++++++---------- src/pickup_meta.rs | 42 ++- src/pickup_meta.rs.in | 5 + src/starting_items.rs | 8 + 8 files changed, 449 insertions(+), 178 deletions(-) diff --git a/ppcasm/ppcasm_macro/src/lib.rs b/ppcasm/ppcasm_macro/src/lib.rs index b4b0ad2c..7bfac7b6 100644 --- a/ppcasm/ppcasm_macro/src/lib.rs +++ b/ppcasm/ppcasm_macro/src/lib.rs @@ -372,6 +372,7 @@ decl_instrs! { addc[o][.], (r:d), (r:a), (r:b) => (6;31) | d | a | b | (?o) | (9;10) | (?.); addi, (r:d), (r:a), (i:imm) => (6;14) | d | a | (16;imm); addic[.], (r:d), (r:a), (i:imm) => (5;6) | (?.) | d | a | (16;imm); + and[.], (r:d), (r:a), (r:b) => (6;31) | d | a | b | (10;28) | (?.); andi, (r:d), (r:a), (i:imm) => (6;28) | d | a | (16;imm); andis, (r:d), (r:a), (i:imm) => (6;29) | d | a | (16;imm); b[l][a], (l:li) => (6;18) | (24;li) | (?a) | (?l); @@ -409,11 +410,14 @@ decl_instrs! { mr, (r:a), (r:s) => (6;31) | s | a | s | (10;444) | (1;0); mtlr, (r:d) => (6;31) | d | (10;0x100) | (10;467) | (1;0); mullw[o][.],(r:d), (r:a), (r:b) => (6;31) | d | a | b | (?o) | (9;235) | (?.); + neg, (r:d), (r:a) => (6;31) | d | a | (16;208); nop => (32;0x60000000); or[.], (r:d), (r:a), (r:b) => (6;31) | d | a | b | (10;444) | (?.); ori, (r:d), (r:a), (i:imm) => (6;24) | d | a | (16;imm); oris, (r:d), (r:a), (i:imm) => (6;25) | d | a | (16;imm); + slw[.], (r:d), (r:a), (r:b) => (6;31) | d | a | b | (10;24) | (?.); slwi, (r:a), (r:s), (i:n) => (6;21) | s | a | (5;{#n}) | (5;0) |(5;{31 - #n}) | (1;0); + srw[.], (r:d), (r:a), (r:b) => (6;31) | d | a | b | (10;536) | (?.); srwi, (r:a), (r:s), (i:n) => (6;21) | s | a | (5;{32 - #n}) | (5;n) |(5;31) | (1;0); rlwimi[.], (r:a), (r:s), (i:sh), (i:mb), (i:me) => (6;20) | s | a | (5;sh) | (5;mb) |(5;me) | (?.); diff --git a/schema/randomprime.schema.json b/schema/randomprime.schema.json index 27eebd13..7831a8c6 100644 --- a/schema/randomprime.schema.json +++ b/schema/randomprime.schema.json @@ -470,9 +470,10 @@ "default": false }, "enableIceTraps": { - "description": "Set to true if Ice Traps items are used in this layout.", + "description": "[Deprecated] Does not do anything anymore", "type": "boolean", - "default": false + "default": false, + "deprecated": true }, "missileStationPbRefill": { "description": "If enabled, Missile Stations also refill Power Bomb ammunition.", @@ -2535,6 +2536,12 @@ "flamethrower": { "type": "boolean", "default": false + }, + "unknown1": { + "type": "integer", + "minimum": 0, + "maximum": 2147483647, + "default": 12 } }, "required": [ @@ -2562,7 +2569,8 @@ "superMissile", "wavebuster", "iceSpreader", - "flamethrower" + "flamethrower", + "unknown1" ], "default": { "combatVisor": true, @@ -2589,7 +2597,8 @@ "superMissile": false, "wavebuster": false, "iceSpreader": false, - "flamethrower": false + "flamethrower": false, + "unknown1": 12, }, "additionalProperties": false }, @@ -2738,7 +2747,12 @@ "Artifact of Newborn", "Nothing", "Floaty Jump", - "Ice Trap" + "Ice Trap", + "Unlimited Missiles", + "Unlimited Power Bombs", + "Missile Launcher", + "Main Power Bomb", + "Spring Ball" ], "default": "Nothing" }, @@ -3960,7 +3974,12 @@ "ArtifactOfSun", "ArtifactOfWorld", "ArtifactOfSpirit", - "ArtifactOfNewborn" + "ArtifactOfNewborn", + "UnlimitedMissiles", + "UnlimitedPowerBombs", + "MissileLauncher", + "PowerBombLauncher", + "SpringBall" ], "default": "PowerBeam" }, diff --git a/src/custom_assets.rs b/src/custom_assets.rs index ba8fc84a..e007112b 100644 --- a/src/custom_assets.rs +++ b/src/custom_assets.rs @@ -1367,16 +1367,14 @@ pub fn collect_game_resources<'r>( ]; looking_for.extend(custom_scan_point_deps); - if config.enable_ice_traps { - let player_freeze_deps: Vec<(u32, FourCC)> = vec![ - resource_info!("breakFreezeVisor.PART").into(), - resource_info!("Frost1TXTR.TXTR").into(), - resource_info!("75DAC95C.PART").into(), - resource_info!("zorch1_snow3.TXTR").into(), - resource_info!("C28C7348.PART").into(), - ]; - looking_for.extend(player_freeze_deps); - } + let player_freeze_deps: Vec<(u32, FourCC)> = vec![ + resource_info!("breakFreezeVisor.PART").into(), + resource_info!("Frost1TXTR.TXTR").into(), + resource_info!("75DAC95C.PART").into(), + resource_info!("zorch1_snow3.TXTR").into(), + resource_info!("C28C7348.PART").into(), + ]; + looking_for.extend(player_freeze_deps); // Dependencies read from paks and custom assets will go here // let mut found = HashMap::with_capacity(looking_for.len()); diff --git a/src/patch_config.rs b/src/patch_config.rs index 08e762ed..306e6361 100644 --- a/src/patch_config.rs +++ b/src/patch_config.rs @@ -1439,7 +1439,7 @@ pub struct PatchConfig { pub starting_visor: Visor, pub starting_beam: Beam, pub escape_sequence_counts_up: bool, - pub enable_ice_traps: bool, + pub enable_ice_traps: bool, // deprecated pub missile_station_pb_refill: bool, pub door_open_mode: DoorOpenMode, @@ -2336,7 +2336,12 @@ impl PatchConfigPrivate { item_max_capacity.insert(PickupType::EnergyTank, 200); } - if item_max_capacity.contains_key(&PickupType::Nothing) + if item_max_capacity.contains_key(&PickupType::UnlimitedMissiles) + || item_max_capacity.contains_key(&PickupType::UnlimitedPowerBombs) + || item_max_capacity.contains_key(&PickupType::MissileLauncher) + || item_max_capacity.contains_key(&PickupType::PowerBombLauncher) + || item_max_capacity.contains_key(&PickupType::SpringBall) + || item_max_capacity.contains_key(&PickupType::Nothing) || item_max_capacity.contains_key(&PickupType::FloatyJump) || item_max_capacity.contains_key(&PickupType::IceTrap) { @@ -2674,7 +2679,7 @@ impl PatchConfigPrivate { .unwrap_or_else(|| StartingItems::from_u64(1)), disable_item_loss: self.game_config.disable_item_loss.unwrap_or(true), escape_sequence_counts_up: self.game_config.escape_sequence_counts_up.unwrap_or(false), - enable_ice_traps: self.game_config.enable_ice_traps.unwrap_or(false), + enable_ice_traps: self.game_config.enable_ice_traps.unwrap_or(true), missile_station_pb_refill: self.game_config.missile_station_pb_refill.unwrap_or(false), door_open_mode: self .game_config diff --git a/src/patches.rs b/src/patches.rs index f985920b..d4e804f4 100644 --- a/src/patches.rs +++ b/src/patches.rs @@ -4173,7 +4173,6 @@ fn modify_pickups_in_mrea<'r>( let mut special_fn_artifact_layer_change_id = 0; let mut trigger_id = 0; let mut floaty_contraption_id = [0, 0, 0, 0]; - let mut special_fn_ice_trap_id = 0; let pickup_kind = pickup_type.kind(); if (29..=40).contains(&pickup_kind) { @@ -4194,10 +4193,6 @@ fn modify_pickups_in_mrea<'r>( ]; } - if pickup_type == PickupType::IceTrap { - special_fn_ice_trap_id = area.new_object_id_from_layer_id(0); - } - let four_ids = [ area.new_object_id_from_layer_id(0), area.new_object_id_from_layer_id(0), @@ -4353,27 +4348,6 @@ fn modify_pickups_in_mrea<'r>( }); } - // If this is an ice trap, insert a special function to freeze the player on picking up - // Extra dependencies for the freeze effect - // steamTxtr -> "Frost1TXTR.TXTR" - // iceTxtr -> "breakFreezeVisor.PART" - if pickup_type == PickupType::IceTrap { - let function = structs::SclyObject { - instance_id: special_fn_ice_trap_id, - property_data: structs::SpecialFunction::ice_trap_fn( - b"Ice Trap Special Function\0".as_cstr(), - ) - .into(), - connections: vec![].into(), - }; - layers[0].objects.as_mut_vec().push(function); - additional_connections.push(structs::Connection { - state: structs::ConnectionState::ARRIVED, - message: structs::ConnectionMsg::ACTION, - target_object_id: special_fn_ice_trap_id, - }); - } - if respawn || mrea_id == 0x40C548E9 { if auto_respawn_timer_id != 0 { let timer = structs::SclyObject { @@ -9776,7 +9750,6 @@ fn patch_dol( smoother_teleports: bool, skip_splash_screens: bool, escape_sequence_counts_up: bool, - enable_ice_traps: bool, uuid: Option<[u8; 16]>, shoot_in_grapple: bool, ) -> Result<(), String> { @@ -11412,120 +11385,352 @@ fn patch_dol( new_text_section.extend(spring_ball_cooldown_reset_on_morph_patch.encoded_bytes()); } - if enable_ice_traps { - let switch_case_ice_trap_patch = ppcasm!(symbol_addr!("Case0_Switch_AcceptScriptMsg_CScriptSpecialFunction", version) + 33 * 4, { - .long new_text_section_end; + // custom item support + let first_custom_item_idx = -1 * (PickupType::ArtifactOfNewborn.kind() + 1) as i32; + let custom_item_initialize_power_up_hook = ppcasm!(symbol_addr!("InitializePowerUp__12CPlayerStateFQ212CPlayerState9EItemTypei", version) + 0x1c, { + b { new_text_section_end }; + }); + dol_patcher.ppcasm_patch(&custom_item_initialize_power_up_hook)?; + + let custom_item_initialize_power_up_patch = ppcasm!(new_text_section_end, { + mr r29, r4; + mr r14, r5; + lis r15, { symbol_addr!("CPlayerState_PowerUpMaxValues", version) }@h; + addi r15, r15, { symbol_addr!("CPlayerState_PowerUpMaxValues", version) }@l; + + // add to item total if pickup isn't disappearing + lwz r3, 0x14(r1); + lwz r3, 0x26c(r3); + cmpwi r3, 0; + bne check_custom_item; + li r3, { PickupType::PowerSuit.kind() }; + rlwinm r0, r3, 0x3, 0x0, 0x1c; + add r3, r31, r0; + addi r3, r3, 0x28; + lwz r4, 0x4(r3); + addi r4, r4, 1; + stw r4, 0x4(r3); + + // add/remove custom item to unknown item 2 + check_custom_item: + cmpwi r29, { PickupType::ArtifactOfNewborn.kind() }; + ble continue_init_power_up; + li r3, { PickupType::UnknownItem2.kind() }; + rlwinm r0, r3, 0x3, 0x0, 0x1c; + add r3, r31, r0; + addi r3, r3, 0x28; + li r4, { first_custom_item_idx }; + add r4, r4, r29; + li r0, 1; + slw r0, r4, r4; + lwz r0, 0x0(r3); + cmpwi r14, 0; + blt remove_custom_item; + or r0, r4, r4; + b set_custom_item; + remove_custom_item: + neg r0, r0; + and r0, r4, r4; + set_custom_item: + stw r4, 0x0(r3); + + // check if it is missile launcher + cmpwi r29, { PickupType::MissileLauncher.kind() }; + bne check_power_bomb; + li r3, { PickupType::Missile.kind() }; + rlwinm r0, r3, 0x3, 0x0, 0x1c; + add r3, r31, r0; + addi r3, r3, 0x28; + lwz r4, 0x4(r3); + add r4, r4, r14; + lwz r0, { PickupType::Missile.kind() * 4 }(r15); + b incr_capacity; + + // check if it is power bomb launcher + check_power_bomb: + cmpwi r29, { PickupType::PowerBombLauncher.kind() }; + bne check_ice_trap; + li r3, { PickupType::PowerBomb.kind() }; + rlwinm r0, r13, 0x3, 0x0, 0x1c; + add r3, r31, r0; + addi r3, r3, 0x28; + lwz r4, 0x4(r3); + add r4, r4, r14; + lwz r0, { PickupType::PowerBomb.kind() * 4 }(r15); + b incr_capacity; + + // check if it is ice trap + check_ice_trap: + cmpwi r29, { PickupType::IceTrap.kind() }; + bne continue_init_power_up; + mr r16, r5; + lwz r3, 0x84c(r25); + mr r4, r25; + lis r5, 0x6FC0; + ori r5, r5, 0x3D46; + li r6, 0xC34; + lis r7, 0x2B75; + ori r7, r7, 0x7945; + bl { symbol_addr!("Freeze__7CPlayerFR13CStateManagerUiUsUi", version) }; + lis r5, data@h; + addi r5, r5, data@l; + lfs f14, 0x0(r5); + lwz r5, 0x8b8(r25); + lwz r5, 0x0(r5); + lfs f15, 0x0c(r5); + fsubs f15, f15, f14; + stfs f15, 0x0c(r5); + fcmpu cr0, f15, f28; + bgt not_dead_from_ice_trap; + lwz r4, 0x0(r5); + andis r4, r4, 0x7fff; + stw r4, 0x0(r5); + not_dead_from_ice_trap: + b end_init_power_up; + + // check for max capacity + incr_capacity: + cmpw r4, r0; + ble incr_capacity_check_for_negative; + mr r4, r0; + b incr_capacity_set_capacity; + incr_capacity_check_for_negative: + cmpwi r4, 0; + bge incr_capacity_set_capacity; + li r4, 0; + incr_capacity_set_capacity: + stw r4, 0x4(r3); + + // check for max amount + lwz r4, 0x0(r3); + add r4, r4, r14; + lwz r0, 0x4(r3); + cmpw r4, r0; + ble incr_amount_check_for_negative; + mr r4, r0; + b incr_amount_set_amount; + incr_amount_check_for_negative: + cmpwi r4, 0; + bge incr_amount_set_amount; + li r4, 0; + incr_amount_set_amount: + stw r4, 0x0(r3); + + end_init_power_up: + mr r5, r14; + andi r14, r14, 0; + andi r15, r15, 0; + andi r16, r16, 0; + fmr f14, f28; + fmr f15, f28; + b { symbol_addr!("InitializePowerUp__12CPlayerStateFQ212CPlayerState9EItemTypei", version) + 0x108 }; + + // restore previous context + continue_init_power_up: + mr r5, r14; + andi r14, r14, 0; + andi r15, r15, 0; + andi r16, r16, 0; + fmr f14, f28; + fmr f15, f28; + cmpwi r29, 0; + b { symbol_addr!("InitializePowerUp__12CPlayerStateFQ212CPlayerState9EItemTypei", version) + 0x20 }; + data: + .float 75.0; }); - dol_patcher.ppcasm_patch(&switch_case_ice_trap_patch)?; - - if version == Version::NtscJ || version == Version::Pal { - let ice_trap_special_func_patch = ppcasm!(new_text_section_end, { - // backup return to case 0 - lwz r16, 0x0(r3); - // if message not Action then return - cmpwi r29, 19; - bne { new_text_section_end + 0x8c }; - // backup "this" pointer - mr r14, r3; - mr r15, r5; - - // function body - lwz r3, 0x84c(r25); - mr r4, r25; - lis r5, 0x6FC0; - ori r5, r5, 0x3D46; - li r6, 0xC34; - lis r7, 0x2B75; - ori r7, r7, 0x7945; - bl { symbol_addr!("Freeze__7CPlayerFR13CStateManagerUiUsUi", version) }; - lis r5, data@h; - addi r5, r5, data@l; - lfs f14, 0x0(r5); - lwz r5, 0x8b8(r25); - lwz r5, 0x0(r5); - lfs f15, 0x0c(r5); - fsubs f15, f15, f14; - stfs f15, 0x0c(r5); - fcmpu cr0, f15, f28; - bgt { new_text_section_end + 0x68 }; - lwz r4, 0x0(r5); - andis r4, r4, 0x7fff; - stw r4, 0x0(r5); - - // restore registers - fmr f14, f28; - fmr f15, f28; - mr r3, r14; - andi r14, r14, 0; - mr r4, r28; - mr r5, r15; - andi r15, r15, 0; - mr r6, r25; - mr r7, r25; - mtlr r16; - andi r16, r16, 0; - blr; - data: - .float 75.0; - }); - new_text_section_end += ice_trap_special_func_patch.encoded_bytes().len() as u32; - new_text_section.extend(ice_trap_special_func_patch.encoded_bytes()); - } else { - let ice_trap_special_func_patch = ppcasm!(new_text_section_end, { - // backup return to case 0 - lwz r16, 0x0(r3); - // if message not Action then return - cmpwi r28, 19; - bne { new_text_section_end + 0x8c }; - // backup "this" pointer - mr r14, r3; - mr r15, r5; - - // function body - lwz r3, 0x84c(r25); - mr r4, r25; - lis r5, 0x6FC0; - ori r5, r5, 0x3D46; - li r6, 0xC34; - lis r7, 0x2B75; - ori r7, r7, 0x7945; - bl { symbol_addr!("Freeze__7CPlayerFR13CStateManagerUiUsUi", version) }; - lis r5, data@h; - addi r5, r5, data@l; - lfs f14, 0x0(r5); - lwz r5, 0x8b8(r25); - lwz r5, 0x0(r5); - lfs f15, 0x0c(r5); - fsubs f15, f15, f14; - stfs f15, 0x0c(r5); - fcmpu cr0, f15, f28; - bgt { new_text_section_end + 0x68 }; - lwz r4, 0x0(r5); - andis r4, r4, 0x7fff; - stw r4, 0x0(r5); - - // restore registers - fmr f14, f28; - fmr f15, f28; - mr r3, r14; - andi r14, r14, 0; - mr r4, r28; - mr r5, r15; - andi r15, r15, 0; - mr r6, r25; - mr r7, r25; - mtlr r16; - andi r16, r16, 0; - blr; - data: - .float 75.0; - }); + new_text_section_end += custom_item_initialize_power_up_patch.encoded_bytes().len() as u32; + new_text_section.extend(custom_item_initialize_power_up_patch.encoded_bytes()); - new_text_section_end += ice_trap_special_func_patch.encoded_bytes().len() as u32; - new_text_section.extend(ice_trap_special_func_patch.encoded_bytes()); - } - } + let custom_item_has_power_up_hook = ppcasm!(symbol_addr!("HasPowerUp__12CPlayerStateCFQ212CPlayerState9EItemType", version), { + b { new_text_section_end }; + }); + dol_patcher.ppcasm_patch(&custom_item_has_power_up_hook)?; + let custom_item_has_power_up_patch = ppcasm!(new_text_section_end, { + // check custom item in unknown item 2 + cmpwi r4, { PickupType::ArtifactOfNewborn.kind() }; + ble { new_text_section_end + 0x30 }; + li r15, { PickupType::UnknownItem2.kind() }; + rlwinm r0, r15, 0x3, 0x0, 0x1c; + add r15, r3, r0; + addi r15, r15, 0x28; + li r3, { first_custom_item_idx }; + add r3, r3, r4; + lwz r0, 0x0(r15); + srw r0, r3, r3; + andi r15, r15, 0; + blr; + + // restore previous context + andi r15, r15, 0; + cmpwi r4, 0; + b { symbol_addr!("HasPowerUp__12CPlayerStateCFQ212CPlayerState9EItemType", version) + 0x4 }; + }); + + new_text_section_end += custom_item_has_power_up_patch.encoded_bytes().len() as u32; + new_text_section.extend(custom_item_has_power_up_patch.encoded_bytes()); + + let custom_item_get_item_amount_hook = ppcasm!(symbol_addr!("GetItemAmount__12CPlayerStateCFQ212CPlayerState9EItemType", version), { + b { new_text_section_end }; + }); + dol_patcher.ppcasm_patch(&custom_item_get_item_amount_hook)?; + let custom_item_get_item_amount_patch = ppcasm!(new_text_section_end, { + // backup arguments + mr r14, r3; + + // preload unknown item 2 for future checks in the function + li r15, { PickupType::UnknownItem2.kind() }; + rlwinm r0, r15, 0x3, 0x0, 0x1c; + add r15, r3, r0; + addi r15, r15, 0x28; + lwz r15, 0x0(r15); + + cmpwi r4, { PickupType::Missile.kind() }; + bne { new_text_section_end + 0x40 }; + // check for missile launcher + andi r15, r3, { PickupType::MissileLauncher.custom_item_value() }; + cmpwi r3, 0; + beq { new_text_section_end + 0x68 }; + // check for unlimited missiles + andi r15, r3, { PickupType::UnlimitedMissiles.custom_item_value() }; + cmpwi r3, 0; + beq { new_text_section_end + 0x78 }; + li r3, 255; + b { new_text_section_end + 0x6c }; + + cmpwi r4, { PickupType::PowerBomb.kind() }; + bne { new_text_section_end + 0x78 }; + // check for power bomb launcher + andi r15, r3, { PickupType::PowerBombLauncher.custom_item_value() }; + cmpwi r3, 0; + beq { new_text_section_end + 0x68 }; + // check for unlimited power bombs + andi r15, r3, { PickupType::UnlimitedPowerBombs.custom_item_value() }; + cmpwi r3, 0; + beq { new_text_section_end + 0x78 }; + li r3, 8; + b { new_text_section_end + 0x6c }; + + li r3, 0; + + andi r14, r14, 0; + andi r15, r15, 0; + blr; + + // restore previous context + mr r3, r14; + andi r14, r14, 0; + andi r15, r15, 0; + cmpwi r4, 0; + b { symbol_addr!("GetItemAmount__12CPlayerStateCFQ212CPlayerState9EItemType", version) + 0x4 }; + }); + + new_text_section_end += custom_item_get_item_amount_patch.encoded_bytes().len() as u32; + new_text_section.extend(custom_item_get_item_amount_patch.encoded_bytes()); + + let custom_item_get_item_capacity_hook = ppcasm!(symbol_addr!("GetItemCapacity__12CPlayerStateCFQ212CPlayerState9EItemType", version), { + b { new_text_section_end }; + }); + dol_patcher.ppcasm_patch(&custom_item_get_item_capacity_hook)?; + let custom_item_get_item_capacity_patch = ppcasm!(new_text_section_end, { + // backup arguments + mr r14, r3; + + // preload unknown item 2 for future checks in the function + li r15, { PickupType::UnknownItem2.kind() }; + rlwinm r0, r15, 0x3, 0x0, 0x1c; + add r15, r3, r0; + addi r15, r15, 0x28; + lwz r15, 0x0(r15); + + cmpwi r4, { PickupType::Missile.kind() }; + bne { new_text_section_end + 0x40 }; + // check for missile launcher + andi r15, r3, { PickupType::MissileLauncher.custom_item_value() }; + cmpwi r3, 0; + beq { new_text_section_end + 0x68 }; + // check for unlimited missiles + andi r15, r3, { PickupType::UnlimitedMissiles.custom_item_value() }; + cmpwi r3, 0; + beq { new_text_section_end + 0x78 }; + li r3, 255; + b { new_text_section_end + 0x6c }; + + cmpwi r4, { PickupType::PowerBomb.kind() }; + bne { new_text_section_end + 0x78 }; + // check for power bomb launcher + andi r15, r3, { PickupType::PowerBombLauncher.custom_item_value() }; + cmpwi r3, 0; + beq { new_text_section_end + 0x68 }; + // check for unlimited power bombs + andi r15, r3, { PickupType::UnlimitedPowerBombs.custom_item_value() }; + cmpwi r3, 0; + beq { new_text_section_end + 0x78 }; + li r3, 8; + b { new_text_section_end + 0x6c }; + + li r3, 0; + + andi r14, r14, 0; + andi r15, r15, 0; + blr; + + // restore previous context + mr r3, r14; + andi r14, r14, 0; + andi r15, r15, 0; + cmpwi r4, 0; + b { symbol_addr!("GetItemCapacity__12CPlayerStateCFQ212CPlayerState9EItemType", version) + 0x4 }; + }); + + new_text_section_end += custom_item_get_item_capacity_patch.encoded_bytes().len() as u32; + new_text_section.extend(custom_item_get_item_capacity_patch.encoded_bytes()); + + let custom_item_decr_pickup_hook = ppcasm!(symbol_addr!("DecrPickUp__12CPlayerStateFQ212CPlayerState9EItemTypei", version), { + b { new_text_section_end }; + }); + dol_patcher.ppcasm_patch(&custom_item_decr_pickup_hook)?; + let custom_item_decr_pickup_patch = ppcasm!(new_text_section_end, { + // backup arguments + mr r14, r3; + + // preload unknown item 2 for future checks in the function + li r15, { PickupType::UnknownItem2.kind() }; + rlwinm r0, r15, 0x3, 0x0, 0x1c; + add r15, r3, r0; + addi r15, r15, 0x28; + lwz r15, 0x0(r15); + + cmpwi r4, { PickupType::Missile.kind() }; + bne { new_text_section_end + 0x30 }; + // check for unlimited missiles + andi r15, r3, { PickupType::UnlimitedMissiles.custom_item_value() }; + cmpwi r3, 0; + beq { new_text_section_end + 0x54 }; + b { new_text_section_end + 0x48 }; + + cmpwi r4, { PickupType::PowerBomb.kind() }; + bne { new_text_section_end + 0x54 }; + // check for unlimited power bombs + andi r15, r3, { PickupType::UnlimitedPowerBombs.custom_item_value() }; + cmpwi r3, 0; + beq { new_text_section_end + 0x54 }; + b { new_text_section_end + 0x48 }; + + andi r14, r14, 0; + andi r15, r15, 0; + blr; + + // restore previous context + mr r3, r14; + andi r14, r14, 0; + andi r15, r15, 0; + cmpwi r4, 0; + b { symbol_addr!("DecrPickUp__12CPlayerStateFQ212CPlayerState9EItemTypei", version) + 0x4 }; + }); + + new_text_section_end += custom_item_decr_pickup_patch.encoded_bytes().len() as u32; + new_text_section.extend(custom_item_decr_pickup_patch.encoded_bytes()); // restore chest vulnerability to missile and charged shot, also wavebuster cheese works too if [Version::Pal, Version::NtscJ].contains(&version) { @@ -16107,11 +16312,9 @@ fn build_and_run_patches<'r>( let mut patcher = PrimePatcher::new(); // Add the freeze effect assets required by CPlayer::Freeze() - if config.enable_ice_traps { - patcher.add_file_patch(b"GGuiSys.pak", |file| { - add_player_freeze_assets(file, game_resources) - }); - } + patcher.add_file_patch(b"GGuiSys.pak", |file| { + add_player_freeze_assets(file, game_resources) + }); // Add the pickup icon patcher.add_file_patch(b"GGuiSys.pak", |file| add_map_pickup_icon_txtr(file)); @@ -17043,6 +17246,11 @@ fn build_and_run_patches<'r>( pickups[idx].clone() // TODO: cloning is suboptimal } }; + + if pickup.pickup_type == "Unknown Item 2" { + panic!("Unknown Item 2 is no more possible to be used directly. If you wish to use custom items then specify their type instead!"); + } + let show_icon = pickup.show_icon.unwrap_or(false); let key = PickupHashKey { @@ -17070,12 +17278,6 @@ fn build_and_run_patches<'r>( } }; - if !config.enable_ice_traps - && PickupType::from_str(&pickup.pickup_type) == PickupType::IceTrap - { - panic!("EnableIceTraps must be true if you are placing Ice Trap pickups"); - } - // modify pickup, connections, hudmemo etc. patcher.add_scly_patch( (pak_name.as_bytes(), room_info.room_id.to_u32()), @@ -17154,12 +17356,6 @@ fn build_and_run_patches<'r>( } }; - if !config.enable_ice_traps - && PickupType::from_str(&pickup.pickup_type) == PickupType::IceTrap - { - panic!("EnableIceTraps must be true if you are placing Ice Trap pickups"); - } - patcher.add_scly_patch( (pak_name.as_bytes(), room_info.room_id.to_u32()), move |_ps, area| { @@ -17636,7 +17832,6 @@ fn build_and_run_patches<'r>( true, config.skip_splash_screens, config.escape_sequence_counts_up, - config.enable_ice_traps, config.uuid, config.shoot_in_grapple, ) @@ -17657,7 +17852,6 @@ fn build_and_run_patches<'r>( false, config.skip_splash_screens, config.escape_sequence_counts_up, - config.enable_ice_traps, config.uuid, config.shoot_in_grapple, ) diff --git a/src/pickup_meta.rs b/src/pickup_meta.rs index 27046717..f8372031 100644 --- a/src/pickup_meta.rs +++ b/src/pickup_meta.rs @@ -38,7 +38,7 @@ pub enum PickupType { EnergyTank, UnknownItem1, HealthRefill, - UnknownItem2, + UnknownItem2, // now used for custom items Wavebuster, ArtifactOfTruth, ArtifactOfStrength, @@ -52,6 +52,11 @@ pub enum PickupType { ArtifactOfWorld, ArtifactOfSpirit, ArtifactOfNewborn, + UnlimitedMissiles, + UnlimitedPowerBombs, + MissileLauncher, + PowerBombLauncher, + SpringBall, Nothing, FloatyJump, IceTrap, @@ -101,6 +106,11 @@ impl PickupType { PickupType::ArtifactOfWorld => "Artifact Of World", PickupType::ArtifactOfSpirit => "Artifact Of Spirit", PickupType::ArtifactOfNewborn => "Artifact Of Newborn", + PickupType::UnlimitedMissiles => "Unlimited Missiles", + PickupType::UnlimitedPowerBombs => "Unlimited Power Bombs", + PickupType::MissileLauncher => "Missile Launcher", + PickupType::PowerBombLauncher => "Main Power Bomb", + PickupType::SpringBall => "Spring Ball", PickupType::Nothing => "Nothing", PickupType::FloatyJump => "Floaty Jump", PickupType::IceTrap => "Ice Trap", @@ -150,6 +160,11 @@ impl PickupType { PickupType::ArtifactOfWorld, PickupType::ArtifactOfSpirit, PickupType::ArtifactOfNewborn, + PickupType::UnlimitedMissiles, + PickupType::UnlimitedPowerBombs, + PickupType::MissileLauncher, + PickupType::PowerBombLauncher, + PickupType::SpringBall, PickupType::Nothing, PickupType::FloatyJump, PickupType::IceTrap, @@ -161,11 +176,19 @@ impl PickupType { pub fn kind(&self) -> u32 { match self { PickupType::FloatyJump => PickupType::Nothing.kind(), - PickupType::IceTrap => PickupType::Nothing.kind(), _ => *self as u32, } } + pub fn custom_item_value(&self) -> i32 + { + if self.kind() <= PickupType::ArtifactOfNewborn.kind() || self.kind() >= PickupType::Nothing.kind() { + panic!("PickupType needs to be a custom item to return a custom item value"); + } + + return 2_i32.pow(self.kind() - PickupType::ArtifactOfNewborn.kind() - 1) as i32; + } + #[allow(clippy::should_implement_trait)] pub fn from_str(string: &str) -> Self { let string = string.to_lowercase(); @@ -261,6 +284,11 @@ pub fn pickup_type_for_pickup(pickup: &structs::Pickup) -> Option { 36 => Some(PickupType::ArtifactOfNature), 30 => Some(PickupType::ArtifactOfStrength), 26 if pickup.curr_increase == 20 => Some(PickupType::HealthRefill), + 41 => Some(PickupType::UnlimitedMissiles), + 42 => Some(PickupType::UnlimitedPowerBombs), + 43 => Some(PickupType::MissileLauncher), + 44 => Some(PickupType::PowerBombLauncher), + 47 => Some(PickupType::IceTrap), _ => None, } } @@ -306,6 +334,11 @@ pub fn pickup_model_for_pickup(pickup: &structs::Pickup) -> Option 36 => Some(PickupModel::ArtifactOfNature), 30 => Some(PickupModel::ArtifactOfStrength), 26 if pickup.curr_increase == 20 => Some(PickupModel::HealthRefill), + 41 => Some(PickupModel::MissileRefill), + 42 => Some(PickupModel::PowerBombRefill), + 43 => Some(PickupModel::Missile), + 44 => Some(PickupModel::PowerBomb), + 47 => Some(PickupModel::IceTrap), _ => None, } } @@ -569,6 +602,11 @@ impl PickupModel { PickupType::ArtifactOfWorld => PickupModel::ArtifactOfWorld, PickupType::ArtifactOfSpirit => PickupModel::ArtifactOfSpirit, PickupType::ArtifactOfNewborn => PickupModel::ArtifactOfNewborn, + PickupType::UnlimitedMissiles => PickupModel::MissileRefill, + PickupType::UnlimitedPowerBombs => PickupModel::PowerBombRefill, + PickupType::MissileLauncher => PickupModel::Missile, + PickupType::PowerBombLauncher => PickupModel::PowerBomb, + PickupType::SpringBall => PickupModel::RandovaniaGamecube, PickupType::Nothing => PickupModel::Nothing, PickupType::FloatyJump => PickupModel::RandovaniaGamecube, PickupType::IceTrap => PickupModel::IceTrap, diff --git a/src/pickup_meta.rs.in b/src/pickup_meta.rs.in index 4c1adef0..adc7e9ff 100644 --- a/src/pickup_meta.rs.in +++ b/src/pickup_meta.rs.in @@ -10359,6 +10359,11 @@ impl PickupType PickupType::ArtifactOfWorld => "audio/jin_artifact.dsp\0", PickupType::ArtifactOfSpirit => "audio/jin_artifact.dsp\0", PickupType::ArtifactOfNewborn => "audio/jin_artifact.dsp\0", + PickupType::UnlimitedMissiles => "/audio/itm_x_short_02.dsp\0", + PickupType::UnlimitedPowerBombs => "/audio/itm_x_short_02.dsp\0", + PickupType::MissileLauncher => "audio/jin_itemattain.dsp\0", + PickupType::PowerBombLauncher => "audio/jin_itemattain.dsp\0", + PickupType::SpringBall => "audio/jin_itemattain.dsp\0", PickupType::Nothing => "/audio/itm_x_short_02.dsp\0", PickupType::FloatyJump => "/audio/itm_x_short_02.dsp\0", PickupType::IceTrap => "/audio/evt_x_event_00.dsp\0", // being a bit trolly here diff --git a/src/starting_items.rs b/src/starting_items.rs index a15f6c7d..065ba160 100644 --- a/src/starting_items.rs +++ b/src/starting_items.rs @@ -1,3 +1,5 @@ +use crate::pickup_meta::PickupType; + use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -28,6 +30,7 @@ pub struct StartingItems { pub wavebuster: bool, pub ice_spreader: bool, pub flamethrower: bool, + pub unknown1: i32, } impl StartingItems { @@ -64,6 +67,7 @@ impl StartingItems { wavebuster: fetch_bits(1) == 1, ice_spreader: fetch_bits(1) == 1, flamethrower: fetch_bits(1) == 1, + unknown1: PickupType::MissileLauncher.custom_item_value() | PickupType::PowerBombLauncher.custom_item_value(), } } @@ -82,6 +86,7 @@ impl StartingItems { spawn_point.bombs = self.bombs as u32; spawn_point.spider_ball = self.spider_ball as u32; spawn_point.boost_ball = self.boost_ball as u32; + spawn_point.power_suit = 0; spawn_point.varia_suit = self.varia_suit as u32; spawn_point.gravity_suit = self.gravity_suit as u32; spawn_point.phazon_suit = self.phazon_suit as u32; @@ -93,6 +98,7 @@ impl StartingItems { spawn_point.wavebuster = self.wavebuster as u32; spawn_point.ice_spreader = self.ice_spreader as u32; spawn_point.flamethrower = self.flamethrower as u32; + spawn_point.unknown1 = self.unknown1 as u32; } /// Custom deserializataion function that accepts an int as well as the usual struct/object @@ -140,6 +146,7 @@ impl StartingItems { && !self.wavebuster && !self.ice_spreader && !self.flamethrower + && self.unknown1 == 0 } } @@ -171,6 +178,7 @@ impl Default for StartingItems { wavebuster: false, ice_spreader: false, flamethrower: false, + unknown1: PickupType::MissileLauncher.custom_item_value() | PickupType::PowerBombLauncher.custom_item_value(), } } }