atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
f48daa8adf591a211fbfdca2c98805b04b0db3c6
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git

Blame: src/game/progress.rs

8b8e83329a1datagen2026-10-12 12:01 UTC1
use super::{
8b8e83329a1datagen2026-10-12 12:01 UTC2
    Floor, Lube, MumVisit, Notice, Observer, Run, Story,
8b8e83329a1datagen2026-10-12 12:01 UTC3
    balance::{
8b8e83329a1datagen2026-10-12 12:01 UTC4
        BRANCH_TURN, CAT_TAKES_TOWEL, CHANGING_ROOM_CHILL, EQ_WOBBLE, EXTENDER_GAIN,
4b1f5f88827datagen2026-10-13 10:18 UTC5
        FORESKIN_GLIDE, FRICTION_BURN, FRICTION_BURN_AT, FRICTION_COLD, FRICTION_DRY,
4b1f5f88827datagen2026-10-13 10:18 UTC6
        FRICTION_GRIP, FRICTION_MAX, FRICTION_SPIT, GIRTH_THICK, HOT_EQ, OBSERVED, PRIAPISM_EQ,
4b1f5f88827datagen2026-10-13 10:18 UTC7
        PRIAPISM_TURNS, PULLED_MUSCLE, PULLED_MUSCLE_INTENSITY, RING_PRIAPISM, RING_TIGHT_PRIAPISM,
4b1f5f88827datagen2026-10-13 10:18 UTC8
        STROKE_EQ, STROKE_EQ_COST, SUSPICION_LIMIT, VIGOUR,
8b8e83329a1datagen2026-10-12 12:01 UTC9
    },
8b8e83329a1datagen2026-10-12 12:01 UTC10
};
3ac624339c98atagen2026-10-13 12:21 UTC11
use crate::problem::Problem;
8b8e83329a1datagen2026-10-12 12:01 UTC12
8b8e83329a1datagen2026-10-12 12:01 UTC13
impl Run {
bf5f8cb6f60fatagen2026-10-13 12:10 UTC14
    pub(super) fn chafe(&mut self, lube: Option<Lube>, intensity: i32, cold: bool) {
8b8e83329a1datagen2026-10-12 12:01 UTC15
        let (base, slip) = match lube {
8b8e83329a1datagen2026-10-12 12:01 UTC16
            None => (FRICTION_DRY, 0),
8b8e83329a1datagen2026-10-12 12:01 UTC17
            Some(Lube::Spit) => (FRICTION_SPIT, 0),
8b8e83329a1datagen2026-10-12 12:01 UTC18
            Some(Lube::Lotion) => (0, 0),
8b8e83329a1datagen2026-10-12 12:01 UTC19
            Some(Lube::BabyOil | Lube::CoconutOil) => (0, 1),
8b8e83329a1datagen2026-10-12 12:01 UTC20
            Some(Lube::Vaseline) => (0, 2),
8b8e83329a1datagen2026-10-12 12:01 UTC21
        };
8b8e83329a1datagen2026-10-12 12:01 UTC22
        let vigour = usize::try_from(intensity - 1).map_or(0, |i| VIGOUR[i.min(VIGOUR.len() - 1)]);
8b8e83329a1datagen2026-10-12 12:01 UTC23
        let unlubed = matches!(lube, None | Some(Lube::Spit));
8b8e83329a1datagen2026-10-12 12:01 UTC24
        let thick = i32::from(unlubed && self.body.girth > GIRTH_THICK);
8b8e83329a1datagen2026-10-12 12:01 UTC25
        let glide = i32::from(unlubed && self.body.foreskin.is_some_and(|f| f >= FORESKIN_GLIDE));
8b8e83329a1datagen2026-10-12 12:01 UTC26
        let mut friction = (base + vigour + thick - slip - glide).max(0);
8b8e83329a1datagen2026-10-12 12:01 UTC27
        if friction > 0
8b8e83329a1datagen2026-10-12 12:01 UTC28
            && self
8b8e83329a1datagen2026-10-12 12:01 UTC29
                .body
8b8e83329a1datagen2026-10-12 12:01 UTC30
                .jelq_grip
8b8e83329a1datagen2026-10-12 12:01 UTC31
                .is_some_and(|grip| grip != self.body.grip)
8b8e83329a1datagen2026-10-12 12:01 UTC32
        {
8b8e83329a1datagen2026-10-12 12:01 UTC33
            friction -= FRICTION_GRIP;
8b8e83329a1datagen2026-10-12 12:01 UTC34
        }
bf5f8cb6f60fatagen2026-10-13 12:10 UTC35
        if cold {
4b1f5f88827datagen2026-10-13 10:18 UTC36
            friction += FRICTION_COLD;
4b1f5f88827datagen2026-10-13 10:18 UTC37
        }
8b8e83329a1datagen2026-10-12 12:01 UTC38
        self.body.jelq_grip = Some(self.body.grip);
8b8e83329a1datagen2026-10-12 12:01 UTC39
        self.body.friction = (self.body.friction + friction).min(FRICTION_MAX);
2d9074e0b4deatagen2026-10-13 12:35 UTC40
        if lube == Some(Lube::Lotion) {
2d9074e0b4deatagen2026-10-13 12:35 UTC41
            if std::mem::take(&mut self.body.conditions.friction_burn) {
2d9074e0b4deatagen2026-10-13 12:35 UTC42
                self.notices.push(Notice::BurnSoothed);
2d9074e0b4deatagen2026-10-13 12:35 UTC43
            }
2d9074e0b4deatagen2026-10-13 12:35 UTC44
        } else if self.body.friction >= FRICTION_BURN_AT
8b8e83329a1datagen2026-10-12 12:01 UTC45
            && !self.body.conditions.friction_burn
8b8e83329a1datagen2026-10-12 12:01 UTC46
            && self.chance(
8b8e83329a1datagen2026-10-12 12:01 UTC47
                self.decision(
8b8e83329a1datagen2026-10-12 12:01 UTC48
                    "friction-burn",
8b8e83329a1datagen2026-10-12 12:01 UTC49
                    "With the skin this raw, has it finally burned?",
8b8e83329a1datagen2026-10-12 12:01 UTC50
                ),
8b8e83329a1datagen2026-10-12 12:01 UTC51
                FRICTION_BURN,
8b8e83329a1datagen2026-10-12 12:01 UTC52
            )
8b8e83329a1datagen2026-10-12 12:01 UTC53
        {
8b8e83329a1datagen2026-10-12 12:01 UTC54
            self.body.conditions.friction_burn = true;
8b8e83329a1datagen2026-10-12 12:01 UTC55
        }
8b8e83329a1datagen2026-10-12 12:01 UTC56
    }
8b8e83329a1datagen2026-10-12 12:01 UTC57
3ac624339c98atagen2026-10-13 12:21 UTC58
    pub(super) fn commit(
3ac624339c98atagen2026-10-13 12:21 UTC59
        &mut self,
3ac624339c98atagen2026-10-13 12:21 UTC60
        intensity: i32,
3ac624339c98atagen2026-10-13 12:21 UTC61
        lube: Option<Lube>,
3ac624339c98atagen2026-10-13 12:21 UTC62
        cost: i32,
3ac624339c98atagen2026-10-13 12:21 UTC63
    ) -> Result<(), Problem> {
3ac624339c98atagen2026-10-13 12:21 UTC64
        self.begin_turn()?;
8b8e83329a1datagen2026-10-12 12:01 UTC65
        self.step_away();
8b8e83329a1datagen2026-10-12 12:01 UTC66
        if self.items.kit.extender {
8b8e83329a1datagen2026-10-12 12:01 UTC67
            self.body.length += EXTENDER_GAIN;
8b8e83329a1datagen2026-10-12 12:01 UTC68
        }
8b8e83329a1datagen2026-10-12 12:01 UTC69
        self.body.stamina -= cost + i32::from(lube == Some(Lube::Lotion));
8b8e83329a1datagen2026-10-12 12:01 UTC70
        if self.body.stamina <= 0 {
8b8e83329a1datagen2026-10-12 12:01 UTC71
            self.exhaust();
8b8e83329a1datagen2026-10-12 12:01 UTC72
        }
8b8e83329a1datagen2026-10-12 12:01 UTC73
        let chill = if self.floor == Floor::ChangingRoom {
8b8e83329a1datagen2026-10-12 12:01 UTC74
            CHANGING_ROOM_CHILL
8b8e83329a1datagen2026-10-12 12:01 UTC75
        } else {
8b8e83329a1datagen2026-10-12 12:01 UTC76
            0
8b8e83329a1datagen2026-10-12 12:01 UTC77
        };
8b8e83329a1datagen2026-10-12 12:01 UTC78
        let wobble = self.oracle.span_int(
8b8e83329a1datagen2026-10-12 12:01 UTC79
            self.decision(
8b8e83329a1datagen2026-10-12 12:01 UTC80
                "wobble",
8b8e83329a1datagen2026-10-12 12:01 UTC81
                "How does the count drift on this stroke, up or down?",
8b8e83329a1datagen2026-10-12 12:01 UTC82
            ),
8b8e83329a1datagen2026-10-12 12:01 UTC83
            -EQ_WOBBLE,
8b8e83329a1datagen2026-10-12 12:01 UTC84
            EQ_WOBBLE,
8b8e83329a1datagen2026-10-12 12:01 UTC85
        );
8b8e83329a1datagen2026-10-12 12:01 UTC86
        self.body.eq =
8b8e83329a1datagen2026-10-12 12:01 UTC87
            (self.body.eq + intensity * STROKE_EQ - STROKE_EQ_COST - chill + wobble).clamp(0, 100);
8b8e83329a1datagen2026-10-12 12:01 UTC88
8b8e83329a1datagen2026-10-12 12:01 UTC89
        self.make_noise(intensity / 5 + i32::from(lube == Some(Lube::Vaseline)));
8b8e83329a1datagen2026-10-12 12:01 UTC90
        if self.floor == Floor::Nans && lube == Some(Lube::CoconutOil) {
8b8e83329a1datagen2026-10-12 12:01 UTC91
            self.add_suspicion(2);
8b8e83329a1datagen2026-10-12 12:01 UTC92
        }
8b8e83329a1datagen2026-10-12 12:01 UTC93
8b8e83329a1datagen2026-10-12 12:01 UTC94
        if intensity >= PULLED_MUSCLE_INTENSITY
8b8e83329a1datagen2026-10-12 12:01 UTC95
            && !self.body.conditions.pulled_muscle
8b8e83329a1datagen2026-10-12 12:01 UTC96
            && self.oracle.chance_not(
8b8e83329a1datagen2026-10-12 12:01 UTC97
                self.decision("pulled-muscle", "Do the muscles hold?"),
8b8e83329a1datagen2026-10-12 12:01 UTC98
                PULLED_MUSCLE,
8b8e83329a1datagen2026-10-12 12:01 UTC99
            )
8b8e83329a1datagen2026-10-12 12:01 UTC100
        {
8b8e83329a1datagen2026-10-12 12:01 UTC101
            self.body.conditions.pulled_muscle = true;
8b8e83329a1datagen2026-10-12 12:01 UTC102
            self.body.stamina = self.body.stamina.min(self.max_stamina());
8b8e83329a1datagen2026-10-12 12:01 UTC103
        }
8b8e83329a1datagen2026-10-12 12:01 UTC104
        self.settle();
3ac624339c98atagen2026-10-13 12:21 UTC105
        Ok(())
8b8e83329a1datagen2026-10-12 12:01 UTC106
    }
8b8e83329a1datagen2026-10-12 12:01 UTC107
8b8e83329a1datagen2026-10-12 12:01 UTC108
    pub(super) fn make_noise(&mut self, noise: i32) {
8b8e83329a1datagen2026-10-12 12:01 UTC109
        let doubled = match self.floor {
8b8e83329a1datagen2026-10-12 12:01 UTC110
            Floor::Nans => true,
8b8e83329a1datagen2026-10-12 12:01 UTC111
            Floor::ChangingRoom => !self.holds_own_in_the_locker_room(),
8b8e83329a1datagen2026-10-12 12:01 UTC112
            _ => false,
8b8e83329a1datagen2026-10-12 12:01 UTC113
        };
8b8e83329a1datagen2026-10-12 12:01 UTC114
        self.add_suspicion(if doubled { noise * 2 } else { noise });
8b8e83329a1datagen2026-10-12 12:01 UTC115
    }
8b8e83329a1datagen2026-10-12 12:01 UTC116
8b8e83329a1datagen2026-10-12 12:01 UTC117
    pub(super) fn add_suspicion(&mut self, amount: i32) {
8b8e83329a1datagen2026-10-12 12:01 UTC118
        self.standing.suspicion = u8::try_from(
8b8e83329a1datagen2026-10-12 12:01 UTC119
            (i32::from(self.standing.suspicion) + amount).clamp(0, i32::from(SUSPICION_LIMIT)),
8b8e83329a1datagen2026-10-12 12:01 UTC120
        )
8b8e83329a1datagen2026-10-12 12:01 UTC121
        .unwrap_or(SUSPICION_LIMIT);
8b8e83329a1datagen2026-10-12 12:01 UTC122
    }
8b8e83329a1datagen2026-10-12 12:01 UTC123
8b8e83329a1datagen2026-10-12 12:01 UTC124
    pub(super) fn settle(&mut self) {
8b8e83329a1datagen2026-10-12 12:01 UTC125
        self.maybe_summon_mum();
8b8e83329a1datagen2026-10-12 12:01 UTC126
        if let Some(observer) = self.floor.observer()
8b8e83329a1datagen2026-10-12 12:01 UTC127
            && self.chance(
8b8e83329a1datagen2026-10-12 12:01 UTC128
                self.decision("observed", "Has someone quietly noticed what is going on?"),
8b8e83329a1datagen2026-10-12 12:01 UTC129
                OBSERVED,
8b8e83329a1datagen2026-10-12 12:01 UTC130
            )
8b8e83329a1datagen2026-10-12 12:01 UTC131
        {
8b8e83329a1datagen2026-10-12 12:01 UTC132
            self.observed = Some(observer);
8b8e83329a1datagen2026-10-12 12:01 UTC133
            self.standing.shame += 1;
8b8e83329a1datagen2026-10-12 12:01 UTC134
            if observer == Observer::Cat
8b8e83329a1datagen2026-10-12 12:01 UTC135
                && self.chance(
8b8e83329a1datagen2026-10-12 12:01 UTC136
                    self.decision("cat-takes-towel", "Has the cat taken the towel?"),
8b8e83329a1datagen2026-10-12 12:01 UTC137
                    CAT_TAKES_TOWEL,
8b8e83329a1datagen2026-10-12 12:01 UTC138
                )
8b8e83329a1datagen2026-10-12 12:01 UTC139
            {
8b8e83329a1datagen2026-10-12 12:01 UTC140
                self.here.towel_stolen = true;
8b8e83329a1datagen2026-10-12 12:01 UTC141
            }
8b8e83329a1datagen2026-10-12 12:01 UTC142
        }
8b8e83329a1datagen2026-10-12 12:01 UTC143
        if self.items.kit.ring
8b8e83329a1datagen2026-10-12 12:01 UTC144
            && self.body.eq > HOT_EQ
8b8e83329a1datagen2026-10-12 12:01 UTC145
            && self.chance(
8b8e83329a1datagen2026-10-12 12:01 UTC146
                self.decision(
8b8e83329a1datagen2026-10-12 12:01 UTC147
                    "ring-priapism",
8b8e83329a1datagen2026-10-12 12:01 UTC148
                    "With the ring still on and everything this warm, will it not go down?",
8b8e83329a1datagen2026-10-12 12:01 UTC149
                ),
8b8e83329a1datagen2026-10-12 12:01 UTC150
                if self.body.girth > GIRTH_THICK {
8b8e83329a1datagen2026-10-12 12:01 UTC151
                    RING_TIGHT_PRIAPISM
8b8e83329a1datagen2026-10-12 12:01 UTC152
                } else {
8b8e83329a1datagen2026-10-12 12:01 UTC153
                    RING_PRIAPISM
8b8e83329a1datagen2026-10-12 12:01 UTC154
                },
8b8e83329a1datagen2026-10-12 12:01 UTC155
            )
8b8e83329a1datagen2026-10-12 12:01 UTC156
        {
8b8e83329a1datagen2026-10-12 12:01 UTC157
            self.body.conditions.priapism_turns = PRIAPISM_TURNS;
8b8e83329a1datagen2026-10-12 12:01 UTC158
        }
8b8e83329a1datagen2026-10-12 12:01 UTC159
        if self.body.conditions.priapism_turns > 0 {
8b8e83329a1datagen2026-10-12 12:01 UTC160
            self.body.conditions.priapism_turns -= 1;
8b8e83329a1datagen2026-10-12 12:01 UTC161
            self.body.eq = self.body.eq.max(PRIAPISM_EQ);
8b8e83329a1datagen2026-10-12 12:01 UTC162
        }
8b8e83329a1datagen2026-10-12 12:01 UTC163
        if self.turn == BRANCH_TURN && self.online.story == Story::Unopened {
8b8e83329a1datagen2026-10-12 12:01 UTC164
            self.online.story = Story::Pending;
8b8e83329a1datagen2026-10-12 12:01 UTC165
            self.notices.push(Notice::ThreadHasReplies);
8b8e83329a1datagen2026-10-12 12:01 UTC166
        }
8b8e83329a1datagen2026-10-12 12:01 UTC167
    }
8b8e83329a1datagen2026-10-12 12:01 UTC168
8b8e83329a1datagen2026-10-12 12:01 UTC169
    pub(super) fn maybe_summon_mum(&mut self) {
8b8e83329a1datagen2026-10-12 12:01 UTC170
        let was_below = self.standing.at_turn_start < SUSPICION_LIMIT;
8b8e83329a1datagen2026-10-12 12:01 UTC171
        if !self.floor.has_mum() || self.household.mum.is_some() {
8b8e83329a1datagen2026-10-12 12:01 UTC172
            return;
8b8e83329a1datagen2026-10-12 12:01 UTC173
        }
8b8e83329a1datagen2026-10-12 12:01 UTC174
        let summoned = was_below && self.standing.suspicion == SUSPICION_LIMIT;
8b8e83329a1datagen2026-10-12 12:01 UTC175
        let mut visits = u32::from(self.standing.suspicion) + 1;
8b8e83329a1datagen2026-10-12 12:01 UTC176
        if self.here.brother_told {
8b8e83329a1datagen2026-10-12 12:01 UTC177
            visits *= 2;
8b8e83329a1datagen2026-10-12 12:01 UTC178
        }
8b8e83329a1datagen2026-10-12 12:01 UTC179
        if summoned
8b8e83329a1datagen2026-10-12 12:01 UTC180
            || self.oracle.ratio(
8b8e83329a1datagen2026-10-12 12:01 UTC181
                self.decision(
8b8e83329a1datagen2026-10-12 12:01 UTC182
                    "mum-summon",
8b8e83329a1datagen2026-10-12 12:01 UTC183
                    "With this much noise in the house, is Mum on her way up?",
8b8e83329a1datagen2026-10-12 12:01 UTC184
                ),
8b8e83329a1datagen2026-10-12 12:01 UTC185
                visits,
8b8e83329a1datagen2026-10-12 12:01 UTC186
                super::balance::MUM_VISIT,
8b8e83329a1datagen2026-10-12 12:01 UTC187
            )
8b8e83329a1datagen2026-10-12 12:01 UTC188
        {
8b8e83329a1datagen2026-10-12 12:01 UTC189
            let errand = self.errand(summoned);
8b8e83329a1datagen2026-10-12 12:01 UTC190
            let nonce = self.fresh_nonce();
8b8e83329a1datagen2026-10-12 12:01 UTC191
            self.household.mum = Some(MumVisit {
8b8e83329a1datagen2026-10-12 12:01 UTC192
                errand,
8b8e83329a1datagen2026-10-12 12:01 UTC193
                nonce,
8b8e83329a1datagen2026-10-12 12:01 UTC194
                strikes: 0,
8b8e83329a1datagen2026-10-12 12:01 UTC195
                knocked: false,
8b8e83329a1datagen2026-10-12 12:01 UTC196
            });
8b8e83329a1datagen2026-10-12 12:01 UTC197
        }
8b8e83329a1datagen2026-10-12 12:01 UTC198
    }
8b8e83329a1datagen2026-10-12 12:01 UTC199
}