From c7089d2deb8886f967b9067e500814c20554eee7 Mon Sep 17 00:00:00 2001 From: TotallyNot <44345987+TotallyNot@users.noreply.github.com> Date: Sun, 9 Nov 2025 17:10:54 +0100 Subject: [PATCH] feat: added more passives --- models/src/bundle/passive.rs | 184 +++++++++++++++++++++++++++++++++++ models/src/bundle/player.rs | 6 ++ models/src/dto/player.rs | 20 +++- models/src/dto/weapon.rs | 6 +- src/passives.rs | 154 ++++++++++++++++++++++++++++- src/player/mod.rs | 2 +- 6 files changed, 363 insertions(+), 9 deletions(-) diff --git a/models/src/bundle/passive.rs b/models/src/bundle/passive.rs index ee40aac..a9b633c 100644 --- a/models/src/bundle/passive.rs +++ b/models/src/bundle/passive.rs @@ -1,4 +1,5 @@ use bevy_ecs::{bundle::Bundle, component::Component}; +use strum::EnumIter; use crate::bundle::player::BodyPart; @@ -186,11 +187,194 @@ pub struct Property { #[derive(Component)] #[cfg_attr(feature = "json", derive(serde::Deserialize))] +#[cfg_attr(feature = "json", serde(rename_all = "snake_case"))] pub enum DrugCooldown { Xanax, Vicodin, } +#[derive(Component)] +#[cfg_attr(feature = "json", derive(serde::Deserialize))] +pub struct DrugAddiction { + pub level: u16, +} + +#[cfg_attr(feature = "json", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "json", serde(rename_all = "snake_case"))] +#[derive(Debug, Clone, Copy, Component, EnumIter)] +pub enum Job { + /// 25% enemy speed reduction + AdultNovelties7, + /// 25% epinephrine effect & duration + AmusementPark7, + /// +25% Passive Dexterity + ClothingStore5, + /// +20% Armor Bonus + ClothingStore10, + /// +25% Flamethrower Damage, +10 Flamethrower Accuracy + FireworksStand5, + /// 25% passive strength + FurnitureStore7, + /// 100% fist & kick damage + FurnitureStore10, + /// +25% bonus to Speed + GasStation3, + /// Occasional 20% health regeneration (10% chance per turn) + GasStation5, + /// 50% reduction to Burning damage received + GasStation7, + /// 50% bonus to Burning damage dealt + GasStation10, + /// +25% Passive Dexterity + GentsStripClub3, + /// +50% Tyrosine effect & duration + GentsStripClub5, + /// 1/4 chance to dodge melee attacks + GentsStripClub10, + /// 1 extra clip for guns during attacks + GunShop7, + /// 10% primary & secondary weapon damage + GunShop10, + /// 20% slashing weapon damage + HairSalon10, + /// +25% Passive Defense + LadiesStripClub3, + /// +50% Serotonin effect & duration + LadiesStripClub5, + /// 30% melee damage mitigation + LadiesStripClub10, + /// +50% speed and dexterity when not wearing armor + LingeryStore5, + /// 10% maximum life + MiningCorporation7, + /// +15% passive on all stats + MusicStore10, + /// +50% flash grenade intensity + PrivateSecurityFirm3, + /// 25% full set armor mitigation bonus + PrivateSecurityFirm7, + /// 10% melee weapon damage + Pub3, + /// 10% melee weapon damage + Restaurant3, + /// +3.00 Accuracy + Zoo10, +} + +impl Job { + pub fn has(self, other: Self) -> bool { + matches!( + (self, other), + (Self::AdultNovelties7, Self::AdultNovelties7) + | (Self::AmusementPark7, Self::AmusementPark7) + | ( + Self::ClothingStore5, + Self::ClothingStore5 | Self::ClothingStore10 + ) + | (Self::ClothingStore10, Self::ClothingStore10) + | (Self::FireworksStand5, Self::FireworksStand5) + | ( + Self::FurnitureStore7, + Self::FurnitureStore7 | Self::FurnitureStore10 + ) + | (Self::FurnitureStore10, Self::FurnitureStore10) + | ( + Self::GasStation3, + Self::GasStation3 | Self::GasStation5 | Self::GasStation7 | Self::GasStation10 + ) + | ( + Self::GasStation5, + Self::GasStation5 | Self::GasStation7 | Self::GasStation10 + ) + | (Self::GasStation7, Self::GasStation7 | Self::GasStation10) + | (Self::GasStation10, Self::GasStation10) + | ( + Self::GentsStripClub3, + Self::GentsStripClub3 | Self::GentsStripClub5 | Self::GentsStripClub10 + ) + | ( + Self::GentsStripClub5, + Self::GentsStripClub5 | Self::GentsStripClub10 + ) + | (Self::GentsStripClub10, Self::GentsStripClub10) + | (Self::GunShop7, Self::GunShop7 | Self::GunShop10) + | (Self::GunShop10, Self::GunShop10) + | (Self::HairSalon10, Self::HairSalon10) + | ( + Self::LadiesStripClub3, + Self::LadiesStripClub3 | Self::LadiesStripClub5 | Self::LadiesStripClub10 + ) + | ( + Self::LadiesStripClub5, + Self::LadiesStripClub5 | Self::LadiesStripClub10 + ) + | (Self::LadiesStripClub10, Self::LadiesStripClub10) + | (Self::LingeryStore5, Self::LingeryStore5) + | (Self::MiningCorporation7, Self::MiningCorporation7) + | (Self::MusicStore10, Self::MusicStore10) + | ( + Self::PrivateSecurityFirm3, + Self::PrivateSecurityFirm3 | Self::PrivateSecurityFirm7 + ) + | (Self::PrivateSecurityFirm7, Self::PrivateSecurityFirm7) + | (Self::Pub3, Self::Pub3) + | (Self::Restaurant3, Self::Restaurant3) + | (Self::Zoo10, Self::Zoo10) + ) + } + + pub fn label(self) -> &'static str { + match self { + Self::AdultNovelties7 => "Adult novelties 7*", + Self::AmusementPark7 => "Amusement park 7*", + Self::ClothingStore5 => "Clothing store 5*", + Self::ClothingStore10 => "Clothing store 10*", + Self::FireworksStand5 => "Fireworks stand 5*", + Self::FurnitureStore7 => "Furniture store 7*", + Self::FurnitureStore10 => "Furniture store 10*", + Self::GasStation3 => "Gas station 3*", + Self::GasStation5 => "Gas station 5*", + Self::GasStation7 => "Gas station 7*", + Self::GasStation10 => "Gas station 10*", + Self::GentsStripClub3 => "Gents strip club 3*", + Self::GentsStripClub5 => "Gents strip club 5*", + Self::GentsStripClub10 => "Gents strip club 10*", + Self::GunShop7 => "Gun shop 7*", + Self::GunShop10 => "Gun shop 10*", + Self::HairSalon10 => "Hair salon 10*", + Self::LadiesStripClub3 => "Ladies strip club 3*", + Self::LadiesStripClub5 => "Ladies strip club 5*", + Self::LadiesStripClub10 => "Ladies strip club 10*", + Self::LingeryStore5 => "Lingery store 5*", + Self::MiningCorporation7 => "Mining corporation 7*", + Self::MusicStore10 => "Music store 10*", + Self::PrivateSecurityFirm3 => "Private security firm 3*", + Self::PrivateSecurityFirm7 => "Private security firm 7*", + Self::Pub3 => "Pub 3*", + Self::Restaurant3 => "Restaurant 3*", + Self::Zoo10 => "Zoo 10*", + } + } + + pub fn supported(self) -> bool { + matches!( + self, + Self::AmusementPark7 + | Self::ClothingStore5 + | Self::FurnitureStore7 + | Self::GasStation3 + | Self::GentsStripClub3 + | Self::GentsStripClub5 + | Self::GentsStripClub10 + | Self::LadiesStripClub3 + | Self::LadiesStripClub5 + | Self::LingeryStore5 + | Self::MiningCorporation7 + | Self::MusicStore10 + ) + } +} + #[derive(Clone, Copy)] pub enum EducationPartDamageBonus { Bio2380, diff --git a/models/src/bundle/player.rs b/models/src/bundle/player.rs index c2de1a9..20bc7ad 100644 --- a/models/src/bundle/player.rs +++ b/models/src/bundle/player.rs @@ -149,6 +149,12 @@ pub enum ActionNullification { GentsStripClub, } +#[derive(Component, Debug)] +pub enum BuildUpBonus { + Focus { bonus: f32 }, + Finale { bonus: f32 }, +} + impl PartDamageBonus { pub fn dmg_bonus(&self, part: BodyPart) -> Option { match self { diff --git a/models/src/dto/player.rs b/models/src/dto/player.rs index c3fbcf4..d344fe9 100644 --- a/models/src/dto/player.rs +++ b/models/src/dto/player.rs @@ -3,7 +3,10 @@ use bevy_ecs::prelude::*; use crate::{ bundle::{ armour::PlayerArmour, - passive::{DrugCooldown, Education, FactionUpgrades, Merits, PassiveBundle, Property}, + passive::{ + DrugAddiction, DrugCooldown, Education, FactionUpgrades, Job, Merits, PassiveBundle, + Property, + }, player::{PlayerBundle, PlayerStrategy, Weapons}, stat::{Defence, Dexterity, Speed, StatBundle, Strength}, }, @@ -49,6 +52,9 @@ pub struct PlayerDto { pub faction: Option, pub drug: Option, pub strategy: PlayerStrategy, + pub cooldown: Option, + pub addiction: Option, + pub job: Option, } impl PlayerDto { @@ -118,6 +124,18 @@ impl PlayerDto { commands.insert(armour); } + if let Some(cooldown) = self.cooldown { + commands.insert(cooldown); + } + + if let Some(addiction) = self.addiction { + commands.insert(addiction); + } + + if let Some(job) = self.job { + commands.insert(job); + } + commands } } diff --git a/models/src/dto/weapon.rs b/models/src/dto/weapon.rs index d145659..f42b2c4 100644 --- a/models/src/dto/weapon.rs +++ b/models/src/dto/weapon.rs @@ -296,8 +296,10 @@ impl WeaponDto { 814 => commands.insert(BuffingTemp::Tyrosine), 464 => commands.insert(BuffingTemp::Melatonin), 463 => commands.insert(BuffingTemp::Epinephrine), - // damaging temps always hit the body - _ => commands.insert(NonTargeted), + // exploding temps always hit the body + // TODO: how does that interact with armour? + 242 | 217 | 220 | 221 | 840 => commands.insert(NonTargeted), + _ => &mut commands, }; } diff --git a/src/passives.rs b/src/passives.rs index c00aee4..c4d618f 100644 --- a/src/passives.rs +++ b/src/passives.rs @@ -1,6 +1,8 @@ use bevy_ecs::prelude::*; use proxisim_models::bundle::{ - passive::{DrugCooldown, Education, FactionUpgrades, Merits}, + armour::PlayerArmour, + passive::{DrugAddiction, DrugCooldown, Education, FactionUpgrades, Job, Merits}, + player::ActionNullification, stat::{ AdditiveBonus, CritRate, Defence, Dexterity, MaxHealth, SimpleStatBonus, Speed, Strength, }, @@ -10,7 +12,8 @@ use crate::{ Stages, effect::Effects, player::status_effect::{ - ExtraStatusEffectEffectiveness, Hardened, Hastened, Sharpened, Strengthened, + ExtraStatusEffectDuration, ExtraStatusEffectEffectiveness, Hardened, Hastened, Sharpened, + Strengthened, }, }; @@ -21,11 +24,14 @@ fn spawn_permanent_effects( &Education, &FactionUpgrades, Option<&DrugCooldown>, + Option<&DrugAddiction>, + Option<&Job>, + &PlayerArmour, )>, mut effects: Effects, mut commands: Commands, ) { - for (player, merits, edu, faction, drug_cd) in merit_q.iter() { + for (player, merits, edu, faction, drug_cd, addiction, job, armour) in merit_q.iter() { if merits.brawn > 0 { effects.spawn( AdditiveBonus::::new("brawn", (merits.brawn as f32) * 0.03), @@ -155,6 +161,34 @@ fn spawn_permanent_effects( _ => (), } + if let Some(addiction) = addiction { + effects.spawn( + AdditiveBonus::::new("addiction", -0.01 * addiction.level as f32), + player, + ); + effects.spawn( + AdditiveBonus::::new("addiction", -0.01 * addiction.level as f32), + player, + ); + effects.spawn( + AdditiveBonus::::new("addiction", -0.01 * addiction.level as f32), + player, + ); + effects.spawn( + AdditiveBonus::::new("addiction", -0.01 * addiction.level as f32), + player, + ); + } + + let mut epi_eff = 1.0; + let mut mela_eff = 1.0; + let mut sero_eff = 1.0; + let mut tyro_eff = 1.0; + + let mut epi_dur = 1.0; + let mut sero_dur = 1.0; + let mut tyro_dur = 1.0; + if edu.bio2410 { effects.spawn(SimpleStatBonus::::new("BIO2410", 6), player); } @@ -219,12 +253,16 @@ fn spawn_permanent_effects( effects.spawn(AdditiveBonus::::new("DEF2760", 0.03), player); } if edu.spt2480 { - commands.entity(player).insert(( + epi_eff *= 1.1; + mela_eff *= 1.1; + sero_eff *= 1.1; + tyro_eff *= 1.1; + /* commands.entity(player).insert(( ExtraStatusEffectEffectiveness::<1, Strengthened>::new(1.1), ExtraStatusEffectEffectiveness::<1, Sharpened>::new(1.1), ExtraStatusEffectEffectiveness::<1, Hardened>::new(1.1), ExtraStatusEffectEffectiveness::<1, Hastened>::new(1.1), - )); + )); */ } if edu.spt2490 { effects.spawn(AdditiveBonus::::new("SPT2490", 0.02), player); @@ -234,6 +272,112 @@ fn spawn_permanent_effects( effects.spawn(AdditiveBonus::::new("SPT2500", 0.02), player); effects.spawn(AdditiveBonus::::new("SPT2500", 0.02), player); } + + if let Some(job) = job { + if job.has(Job::AmusementPark7) { + epi_eff *= 1.25; + epi_dur *= 1.25; + } + if job.has(Job::ClothingStore5) { + effects.spawn( + AdditiveBonus::::new("clothing_store", 0.25), + player, + ); + } + if job.has(Job::FurnitureStore7) { + effects.spawn( + AdditiveBonus::::new("furniture_store", 0.25), + player, + ); + } + if job.has(Job::GasStation3) { + effects.spawn(AdditiveBonus::::new("gas_station", 0.25), player); + } + if job.has(Job::GentsStripClub3) { + effects.spawn( + AdditiveBonus::::new("gents_strip_club", 0.25), + player, + ); + } + if job.has(Job::GentsStripClub5) { + tyro_eff *= 1.5; + tyro_dur *= 1.5; + } + if job.has(Job::GentsStripClub10) { + commands + .entity(player) + .with_child(ActionNullification::GentsStripClub); + } + if job.has(Job::LadiesStripClub3) { + effects.spawn( + AdditiveBonus::::new("ladies_strip_club", 0.25), + player, + ); + } + if job.has(Job::LadiesStripClub5) { + sero_eff *= 1.5; + sero_dur *= 1.5; + } + if job.has(Job::LingeryStore5) && armour.into_iter().count() == 0 { + effects.spawn( + AdditiveBonus::::new("lingery_store", 0.50), + player, + ); + effects.spawn(AdditiveBonus::::new("lingery_store", 0.50), player); + } + if job.has(Job::MiningCorporation7) { + effects.spawn( + SimpleStatBonus::::new("mining_corporation", 1.1), + player, + ); + } + if job.has(Job::MusicStore10) { + effects.spawn(AdditiveBonus::::new("music_store", 0.15), player); + effects.spawn(AdditiveBonus::::new("music_store", 0.15), player); + effects.spawn(AdditiveBonus::::new("music_store", 0.15), player); + effects.spawn(AdditiveBonus::::new("music_store", 0.15), player); + } + } + + if epi_eff >= 1.0 { + commands + .entity(player) + .insert(ExtraStatusEffectEffectiveness::<1, Strengthened>::new( + epi_eff, + )); + } + if epi_dur >= 1.0 { + commands + .entity(player) + .insert(ExtraStatusEffectDuration::<1, Strengthened>::new(epi_dur)); + } + if mela_eff >= 1.0 { + commands + .entity(player) + .insert(ExtraStatusEffectEffectiveness::<1, Hastened>::new(mela_eff)); + } + if sero_eff >= 1.0 { + commands + .entity(player) + .insert(ExtraStatusEffectEffectiveness::<1, Hardened>::new(sero_eff)); + } + if sero_dur >= 1.0 { + commands + .entity(player) + .insert(ExtraStatusEffectDuration::<1, Hardened>::new(sero_dur)); + } + if tyro_eff >= 1.0 { + commands + .entity(player) + .insert(ExtraStatusEffectEffectiveness::<1, Sharpened>::new( + tyro_eff, + )); + } + if tyro_dur >= 1.0 { + commands + .entity(player) + .insert(ExtraStatusEffectDuration::<1, Sharpened>::new(tyro_dur)); + } } } diff --git a/src/player/mod.rs b/src/player/mod.rs index 8e9cd8d..2865251 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -366,7 +366,7 @@ fn check_action_nullification( return Some("homerun"); } ActionNullification::GentsStripClub - if player_action.0 == WeaponSlot::Melee && shared.rng.random_bool(0.25) => + if player_action.0 == WeaponSlot::Melee && shared.rng.random_ratio(1, 4) => { return Some("dodged"); }