From bbf56c3a228de486012661b56ff9ae2d8060e2b6 Mon Sep 17 00:00:00 2001 From: Kevin Marsolais Date: Tue, 21 May 2024 11:16:07 -0700 Subject: [PATCH] Additive suit damage reduction (#39) Adds an alternative option for suit damage reduction, "Additive", where each suit gets a specific damage reduction value, and each value is added together to determine total damage reduction. Varia and Gravity Suits each provide +10% damage reduction, and Phazon Suit provides +30% damage reduction. Examples: - Varia or Gravity Suit alone provide 10% total damage reduction - Both Varia and Gravity Suit provide 20% total damage reduction - Phazon Suit and one additional suit provide 40% total damage reduction `staggeredSuitDamage` property is changed to allow "Default", "Progressive", or "Additive" while maintaining backwards compatibility with boolean values. --- ppcasm/ppcasm_macro/src/lib.rs | 1 + schema/randomprime.schema.json | 14 +++-- src/patch_config.rs | 75 +++++++++++++++++++++++++-- src/patches.rs | 93 ++++++++++++++++++++++++---------- 4 files changed, 147 insertions(+), 36 deletions(-) diff --git a/ppcasm/ppcasm_macro/src/lib.rs b/ppcasm/ppcasm_macro/src/lib.rs index a1d87872..b4b0ad2c 100644 --- a/ppcasm/ppcasm_macro/src/lib.rs +++ b/ppcasm/ppcasm_macro/src/lib.rs @@ -410,6 +410,7 @@ decl_instrs! { 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) | (?.); 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); slwi, (r:a), (r:s), (i:n) => (6;21) | s | a | (5;{#n}) | (5;0) |(5;{31 - #n}) | (1;0); diff --git a/schema/randomprime.schema.json b/schema/randomprime.schema.json index 3292a634..b44948df 100644 --- a/schema/randomprime.schema.json +++ b/schema/randomprime.schema.json @@ -358,9 +358,17 @@ "default": false }, "staggeredSuitDamage": { - "description": "Damage reduction depends on how many suits you have, not which.", - "type": "boolean", - "default": false + "description": "Configure how suit damage reduction is calculated.\n- `Default` or `false`: Based on strongest suit.\n- `Progressive` or `true`: Based on number of suits.\n- `Additive`: Individual suits provide added damage reduction.\nNOTE: boolean values are deprecated.", + "type": [ + "string", + "boolean" + ], + "enum": [ + "Default", + "Progressive", + "Additive" + ], + "default": "Default" }, "heatDamagePerSec": { "description": "Configure how much damage per second you take in heated rooms when you don't have the proper protection.", diff --git a/src/patch_config.rs b/src/patch_config.rs index ad5c6f57..e7480c0f 100644 --- a/src/patch_config.rs +++ b/src/patch_config.rs @@ -10,7 +10,10 @@ use clap::{crate_version, App, Arg}; use json_data::*; use json_strip::strip_jsonc_comments; use reader_writer::{FourCC, Reader}; -use serde::{Deserialize, Serialize}; +use serde::{ + de::{Error, Visitor}, + Deserialize, Deserializer, Serialize, +}; use structs::{res_id, MapaObjectVisibilityMode, ResId}; use crate::{ @@ -1111,6 +1114,66 @@ pub enum DifficultyBehavior { HardOnly, } +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] +pub enum SuitDamageReduction { + #[default] + Default, + Progressive, + Additive, +} + +impl<'de> Deserialize<'de> for SuitDamageReduction { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct SuitDamageReductionVisitor; + + impl<'de> Visitor<'de> for SuitDamageReductionVisitor { + type Value = SuitDamageReduction; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!( + formatter, + r#""Default", "Progressive", "Additive", true, or false"# + ) + } + + fn visit_bool(self, v: bool) -> Result + where + E: Error, + { + if v { + Ok(SuitDamageReduction::Progressive) + } else { + Ok(SuitDamageReduction::Default) + } + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + match v { + "Default" => Ok(SuitDamageReduction::Default), + "Progressive" => Ok(SuitDamageReduction::Progressive), + "Additive" => Ok(SuitDamageReduction::Additive), + variant => Err(E::unknown_variant( + variant, + &["Default", "Progressive", "Additive"], + )), + } + } + } + + deserializer.deserialize_enum( + "SuitDamageReduction", + &["Default", "Progressive", "Additive"], + SuitDamageReductionVisitor, + ) + } +} + impl fmt::Display for Version { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match self { @@ -1190,7 +1253,7 @@ pub struct PatchConfig { pub poison_damage_per_sec: f32, pub phazon_damage_per_sec: f32, pub phazon_damage_modifier: PhazonDamageModifier, - pub staggered_suit_damage: bool, + pub staggered_suit_damage: SuitDamageReduction, pub item_max_capacity: HashMap, pub map_default_state: MapaObjectVisibilityMode, pub auto_enabled_elevators: bool, @@ -1283,7 +1346,7 @@ struct GameConfig { shuffle_pickup_pos_all_rooms: Option, remove_vanilla_blast_shields: Option, nonvaria_heat_damage: Option, - staggered_suit_damage: Option, + staggered_suit_damage: Option, heat_damage_per_sec: Option, poison_damage_per_sec: Option, phazon_damage_per_sec: Option, @@ -1577,11 +1640,13 @@ impl PatchConfig { "quickpatch" => patch_config.preferences.quickpatch, "quiet" => patch_config.preferences.quiet, "nonvaria heat damage" => patch_config.game_config.nonvaria_heat_damage, - "staggered suit damage" => patch_config.game_config.staggered_suit_damage, "auto enabled elevators" => patch_config.game_config.auto_enabled_elevators, "spring ball" => patch_config.game_config.spring_ball, "warp to start" => patch_config.game_config.warp_to_start, ); + if matches.is_present("staggered suit damage") { + patch_config.game_config.staggered_suit_damage = Some(SuitDamageReduction::Default); + } // string if let Some(input_iso_path) = matches.value_of("input iso path") { @@ -2362,7 +2427,7 @@ impl PatchConfigPrivate { .remove_vanilla_blast_shields .unwrap_or(false), nonvaria_heat_damage: self.game_config.nonvaria_heat_damage.unwrap_or(false), - staggered_suit_damage: self.game_config.staggered_suit_damage.unwrap_or(false), + staggered_suit_damage: self.game_config.staggered_suit_damage.unwrap_or_default(), heat_damage_per_sec: self.game_config.heat_damage_per_sec.unwrap_or(10.0), poison_damage_per_sec: self.game_config.poison_damage_per_sec.unwrap_or(0.11), phazon_damage_per_sec: self.game_config.phazon_damage_per_sec.unwrap_or(0.964), diff --git a/src/patches.rs b/src/patches.rs index 6ae1e8af..43fe934d 100644 --- a/src/patches.rs +++ b/src/patches.rs @@ -44,7 +44,7 @@ use crate::{ CtwkConfig, CutsceneMode, DifficultyBehavior, DoorConfig, DoorOpenMode, FogConfig, GameBanner, GenericTexture, HallOfTheEldersBombSlotCoversConfig, IsoFormat, LevelConfig, PatchConfig, PhazonDamageModifier, PickupConfig, PlatformConfig, PlatformType, RoomConfig, - RunMode, SpecialFunctionType, Version, Visor, + RunMode, SpecialFunctionType, SuitDamageReduction, Version, Visor, }, patcher::{PatcherState, PrimePatcher}, pickup_meta::{ @@ -10546,33 +10546,70 @@ fn patch_dol( dol_patcher.ppcasm_patch(&heat_damage_patch)?; } - if config.staggered_suit_damage { - let (patch_offset, jump_offset) = if version == Version::Pal || version == Version::NtscJ { - (0x11c, 0x1b8) - } else { - (0x128, 0x1c4) - }; - - let staggered_suit_damage_patch = ppcasm!(symbol_addr!("ApplyLocalDamage__13CStateManagerFRC9CVector3fRC9CVector3fR6CActorfRC11CWeaponMode", version) + patch_offset, { - lwz r3, 0x8b8(r25); - lwz r3, 0(r3); - lwz r4, 220(r3); - lwz r5, 212(r3); - addc r4, r4, r5; - lwz r5, 228(r3); - addc r4, r4, r5; - rlwinm r4, r4, 2, 0, 29; - lis r6, data@h; - addi r6, r6, data@l; - lfsx f0, r4, r6; - b { symbol_addr!("ApplyLocalDamage__13CStateManagerFRC9CVector3fRC9CVector3fR6CActorfRC11CWeaponMode", version) + jump_offset }; - data: - .float 0.0; - .float 0.1; - .float 0.2; - .float 0.5; - }); - dol_patcher.ppcasm_patch(&staggered_suit_damage_patch)?; + match config.staggered_suit_damage { + SuitDamageReduction::Progressive => { + let (patch_offset, jump_offset) = + if version == Version::Pal || version == Version::NtscJ { + (0x11c, 0x1b8) + } else { + (0x128, 0x1c4) + }; + let staggered_suit_damage_patch = ppcasm!(symbol_addr!("ApplyLocalDamage__13CStateManagerFRC9CVector3fRC9CVector3fR6CActorfRC11CWeaponMode", version) + patch_offset, { + lwz r3, 0x8b8(r25); + lwz r3, 0(r3); + lwz r4, 220(r3); + lwz r5, 212(r3); + addc r4, r4, r5; + lwz r5, 228(r3); + addc r4, r4, r5; + rlwinm r4, r4, 2, 0, 29; + lis r6, data@h; + addi r6, r6, data@l; + lfsx f0, r4, r6; + b { symbol_addr!("ApplyLocalDamage__13CStateManagerFRC9CVector3fRC9CVector3fR6CActorfRC11CWeaponMode", version) + jump_offset }; + data: + .float 0.0; + .float 0.1; + .float 0.2; + .float 0.5; + }); + dol_patcher.ppcasm_patch(&staggered_suit_damage_patch)?; + } + SuitDamageReduction::Additive => { + let (patch_offset, jump_offset) = + if version == Version::Pal || version == Version::NtscJ { + (0x11c, 0x1b8) + } else { + (0x128, 0x1c4) + }; + let staggered_suit_damage_patch = ppcasm!(symbol_addr!("ApplyLocalDamage__13CStateManagerFRC9CVector3fRC9CVector3fR6CActorfRC11CWeaponMode", version) + patch_offset, { + lwz r3, 0x8b8(r25); + lwz r3, 0(r3); + lwz r4, 220(r3); + lwz r5, 212(r3); + slwi r5, r5, 1; + or r4, r4, r5; + lwz r5, 228(r3); + slwi r5, r5, 2; + or r4, r4, r5; + rlwinm r4, r4, 2, 0, 29; + lis r6, data@h; + addi r6, r6, data@l; + lfsx f0, r4, r6; + b { symbol_addr!("ApplyLocalDamage__13CStateManagerFRC9CVector3fRC9CVector3fR6CActorfRC11CWeaponMode", version) + jump_offset }; + data: + .float 0.0; // 000 - Power Suit + .float 0.1; // 001 - Varia Suit + .float 0.1; // 010 - Gravity Suit + .float 0.2; // 011 - Varia + Gravity Suit + .float 0.3; // 100 - Phazon Suit + .float 0.4; // 101 - Phazon + Varia Suit + .float 0.4; // 110 - Phazon + Gravity Suit + .float 0.5; // 111 - All Suits + }); + dol_patcher.ppcasm_patch(&staggered_suit_damage_patch)?; + } + SuitDamageReduction::Default => {} } for (pickup_type, value) in &config.item_max_capacity {