an http based roguelike rpg about jelqing
Blame: src/game/progress.rs
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 1 | use super::{ |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 2 | Floor, Lube, MumVisit, Notice, Observer, Run, Story, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 3 | balance::{ |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 4 | BRANCH_TURN, CAT_TAKES_TOWEL, CHANGING_ROOM_CHILL, EQ_WOBBLE, EXTENDER_GAIN, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 5 | FORESKIN_GLIDE, FRICTION_BURN, FRICTION_BURN_AT, FRICTION_DRY, FRICTION_GRIP, FRICTION_MAX, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 6 | FRICTION_SPIT, GIRTH_THICK, HOT_EQ, OBSERVED, PRIAPISM_EQ, PRIAPISM_TURNS, PULLED_MUSCLE, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 7 | PULLED_MUSCLE_INTENSITY, RING_PRIAPISM, RING_TIGHT_PRIAPISM, STROKE_EQ, STROKE_EQ_COST, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 8 | SUSPICION_LIMIT, VIGOUR, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 9 | }, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 10 | }; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 11 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 12 | impl Run { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 13 | pub(super) fn chafe(&mut self, lube: Option<Lube>, intensity: i32) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 14 | let (base, slip) = match lube { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 15 | None => (FRICTION_DRY, 0), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 16 | Some(Lube::Spit) => (FRICTION_SPIT, 0), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 17 | Some(Lube::Lotion) => (0, 0), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 18 | Some(Lube::BabyOil | Lube::CoconutOil) => (0, 1), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 19 | Some(Lube::Vaseline) => (0, 2), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 20 | }; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 21 | let vigour = usize::try_from(intensity - 1).map_or(0, |i| VIGOUR[i.min(VIGOUR.len() - 1)]); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 22 | let unlubed = matches!(lube, None | Some(Lube::Spit)); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 23 | let thick = i32::from(unlubed && self.body.girth > GIRTH_THICK); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 24 | let glide = i32::from(unlubed && self.body.foreskin.is_some_and(|f| f >= FORESKIN_GLIDE)); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 25 | let mut friction = (base + vigour + thick - slip - glide).max(0); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 26 | if friction > 0 |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 27 | && self |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 28 | .body |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 29 | .jelq_grip |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 30 | .is_some_and(|grip| grip != self.body.grip) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 31 | { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 32 | friction -= FRICTION_GRIP; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 33 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 34 | self.body.jelq_grip = Some(self.body.grip); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 35 | self.body.friction = (self.body.friction + friction).min(FRICTION_MAX); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 36 | if self.body.friction >= FRICTION_BURN_AT |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 37 | && !self.body.conditions.friction_burn |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 38 | && self.chance( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 39 | self.decision( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 40 | "friction-burn", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 41 | "With the skin this raw, has it finally burned?", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 42 | ), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 43 | FRICTION_BURN, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 44 | ) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 45 | { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 46 | self.body.conditions.friction_burn = true; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 47 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 48 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 49 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 50 | pub(super) fn commit(&mut self, intensity: i32, lube: Option<Lube>, cost: i32) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 51 | self.begin_turn(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 52 | self.step_away(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 53 | if self.items.kit.extender { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 54 | self.body.length += EXTENDER_GAIN; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 55 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 56 | self.body.stamina -= cost + i32::from(lube == Some(Lube::Lotion)); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 57 | if self.body.stamina <= 0 { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 58 | self.exhaust(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 59 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 60 | let chill = if self.floor == Floor::ChangingRoom { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 61 | CHANGING_ROOM_CHILL |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 62 | } else { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 63 | 0 |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 64 | }; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 65 | let wobble = self.oracle.span_int( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 66 | self.decision( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 67 | "wobble", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 68 | "How does the count drift on this stroke, up or down?", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 69 | ), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 70 | -EQ_WOBBLE, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 71 | EQ_WOBBLE, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 72 | ); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 73 | self.body.eq = |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 74 | (self.body.eq + intensity * STROKE_EQ - STROKE_EQ_COST - chill + wobble).clamp(0, 100); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 75 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 76 | self.make_noise(intensity / 5 + i32::from(lube == Some(Lube::Vaseline))); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 77 | if self.floor == Floor::Nans && lube == Some(Lube::CoconutOil) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 78 | self.add_suspicion(2); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 79 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 80 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 81 | if intensity >= PULLED_MUSCLE_INTENSITY |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 82 | && !self.body.conditions.pulled_muscle |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 83 | && self.oracle.chance_not( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 84 | self.decision("pulled-muscle", "Do the muscles hold?"), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 85 | PULLED_MUSCLE, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 86 | ) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 87 | { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 88 | self.body.conditions.pulled_muscle = true; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 89 | self.body.stamina = self.body.stamina.min(self.max_stamina()); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 90 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 91 | self.settle(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 92 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 93 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 94 | pub(super) fn make_noise(&mut self, noise: i32) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 95 | let doubled = match self.floor { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 96 | Floor::Nans => true, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 97 | Floor::ChangingRoom => !self.holds_own_in_the_locker_room(), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 98 | _ => false, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 99 | }; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 100 | self.add_suspicion(if doubled { noise * 2 } else { noise }); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 101 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 102 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 103 | pub(super) fn add_suspicion(&mut self, amount: i32) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 104 | self.standing.suspicion = u8::try_from( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 105 | (i32::from(self.standing.suspicion) + amount).clamp(0, i32::from(SUSPICION_LIMIT)), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 106 | ) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 107 | .unwrap_or(SUSPICION_LIMIT); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 108 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 109 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 110 | pub(super) fn settle(&mut self) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 111 | self.maybe_summon_mum(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 112 | if let Some(observer) = self.floor.observer() |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 113 | && self.chance( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 114 | self.decision("observed", "Has someone quietly noticed what is going on?"), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 115 | OBSERVED, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 116 | ) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 117 | { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 118 | self.observed = Some(observer); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 119 | self.standing.shame += 1; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 120 | if observer == Observer::Cat |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 121 | && self.chance( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 122 | self.decision("cat-takes-towel", "Has the cat taken the towel?"), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 123 | CAT_TAKES_TOWEL, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 124 | ) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 125 | { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 126 | self.here.towel_stolen = true; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 127 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 128 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 129 | if self.items.kit.ring |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 130 | && self.body.eq > HOT_EQ |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 131 | && self.chance( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 132 | self.decision( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 133 | "ring-priapism", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 134 | "With the ring still on and everything this warm, will it not go down?", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 135 | ), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 136 | if self.body.girth > GIRTH_THICK { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 137 | RING_TIGHT_PRIAPISM |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 138 | } else { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 139 | RING_PRIAPISM |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 140 | }, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 141 | ) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 142 | { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 143 | self.body.conditions.priapism_turns = PRIAPISM_TURNS; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 144 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 145 | if self.body.conditions.priapism_turns > 0 { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 146 | self.body.conditions.priapism_turns -= 1; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 147 | self.body.eq = self.body.eq.max(PRIAPISM_EQ); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 148 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 149 | if self.turn == BRANCH_TURN && self.online.story == Story::Unopened { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 150 | self.online.story = Story::Pending; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 151 | self.notices.push(Notice::ThreadHasReplies); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 152 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 153 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 154 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 155 | pub(super) fn maybe_summon_mum(&mut self) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 156 | let was_below = self.standing.at_turn_start < SUSPICION_LIMIT; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 157 | if !self.floor.has_mum() || self.household.mum.is_some() { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 158 | return; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 159 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 160 | let summoned = was_below && self.standing.suspicion == SUSPICION_LIMIT; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 161 | let mut visits = u32::from(self.standing.suspicion) + 1; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 162 | if self.here.brother_told { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 163 | visits *= 2; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 164 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 165 | if summoned |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 166 | || self.oracle.ratio( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 167 | self.decision( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 168 | "mum-summon", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 169 | "With this much noise in the house, is Mum on her way up?", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 170 | ), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 171 | visits, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 172 | super::balance::MUM_VISIT, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 173 | ) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 174 | { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 175 | let errand = self.errand(summoned); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 176 | let nonce = self.fresh_nonce(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 177 | self.household.mum = Some(MumVisit { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 178 | errand, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 179 | nonce, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 180 | strikes: 0, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 181 | knocked: false, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 182 | }); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 183 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 184 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 185 | } |