atagen

jelq

public

an http based roguelike rpg about jelqing

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

Blame: src/game/hazards.rs

782069860236atagen2026-10-10 12:44 UTC1
use std::{ops::ControlFlow, time::SystemTime};
782069860236atagen2026-10-10 12:44 UTC2
782069860236atagen2026-10-10 12:44 UTC3
use axum::http::{StatusCode, header};
782069860236atagen2026-10-10 12:44 UTC4
use serde_json::json;
782069860236atagen2026-10-10 12:44 UTC5
782069860236atagen2026-10-10 12:44 UTC6
use super::{
782069860236atagen2026-10-10 12:44 UTC7
    Cause, Floor, Outcome, Run,
782069860236atagen2026-10-10 12:44 UTC8
    balance::{
139164489032atagen2026-10-11 05:46 UTC9
        BURN_RUPTURE, COLD_EQ, CURVE_MISSES, DANGER_EQ, DEGLOVING, DEGLOVING_INTENSITY, FRIEND,
139164489032atagen2026-10-11 05:46 UTC10
        HOT_EQ, PREMATURE_EQ, RUPTURE_HOT, RUPTURE_IN_BAND, RUPTURE_PREMATURE, RUPTURE_WARM,
782069860236atagen2026-10-10 12:44 UTC11
        SUSPICION_LIMIT, WANDERER_VISIT, WINDOW,
782069860236atagen2026-10-10 12:44 UTC12
    },
782069860236atagen2026-10-10 12:44 UTC13
    techniques::Training,
782069860236atagen2026-10-10 12:44 UTC14
};
782069860236atagen2026-10-10 12:44 UTC15
use crate::problem::Problem;
782069860236atagen2026-10-10 12:44 UTC16
782069860236atagen2026-10-10 12:44 UTC17
type Interruption = Result<ControlFlow<Outcome>, Problem>;
782069860236atagen2026-10-10 12:44 UTC18
782069860236atagen2026-10-10 12:44 UTC19
impl Run {
b85a0d4536c3atagen2026-10-11 08:50 UTC20
    /// How far the friction burn drags the rupture tiers down.
b85a0d4536c3atagen2026-10-11 08:50 UTC21
    pub(super) fn burn(&self) -> i32 {
b85a0d4536c3atagen2026-10-11 08:50 UTC22
        if self.conditions.friction_burn {
b85a0d4536c3atagen2026-10-11 08:50 UTC23
            BURN_RUPTURE
b85a0d4536c3atagen2026-10-11 08:50 UTC24
        } else {
b85a0d4536c3atagen2026-10-11 08:50 UTC25
            0
b85a0d4536c3atagen2026-10-11 08:50 UTC26
        }
b85a0d4536c3atagen2026-10-11 08:50 UTC27
    }
b85a0d4536c3atagen2026-10-11 08:50 UTC28
b85a0d4536c3atagen2026-10-11 08:50 UTC29
    /// The rupture tier for any turn that is not a jelq, so never the premature band's 1 in 3.
b85a0d4536c3atagen2026-10-11 08:50 UTC30
    pub(super) fn rupture_odds(&self) -> u32 {
b85a0d4536c3atagen2026-10-11 08:50 UTC31
        if self.eq <= DANGER_EQ - self.burn() {
b85a0d4536c3atagen2026-10-11 08:50 UTC32
            RUPTURE_IN_BAND
b85a0d4536c3atagen2026-10-11 08:50 UTC33
        } else if self.eq <= HOT_EQ - self.burn() {
b85a0d4536c3atagen2026-10-11 08:50 UTC34
            RUPTURE_WARM
b85a0d4536c3atagen2026-10-11 08:50 UTC35
        } else {
b85a0d4536c3atagen2026-10-11 08:50 UTC36
            RUPTURE_HOT
b85a0d4536c3atagen2026-10-11 08:50 UTC37
        }
b85a0d4536c3atagen2026-10-11 08:50 UTC38
    }
b85a0d4536c3atagen2026-10-11 08:50 UTC39
782069860236atagen2026-10-10 12:44 UTC40
    /// Accidents tied to a specific technique.
782069860236atagen2026-10-10 12:44 UTC41
    pub(super) fn mishaps(&mut self, training: Training, intensity: i32) -> Interruption {
3f9091557ec3atagen2026-10-11 11:43 UTC42
        if training == Training::Dry
3f9091557ec3atagen2026-10-11 11:43 UTC43
            && intensity >= DEGLOVING_INTENSITY
3f9091557ec3atagen2026-10-11 11:43 UTC44
            && self.chance(
3f9091557ec3atagen2026-10-11 11:43 UTC45
                self.decision("degloving", "Dry at this intensity, does the skin tear?"),
3f9091557ec3atagen2026-10-11 11:43 UTC46
                DEGLOVING,
3f9091557ec3atagen2026-10-11 11:43 UTC47
            )
3f9091557ec3atagen2026-10-11 11:43 UTC48
        {
782069860236atagen2026-10-10 12:44 UTC49
            return Err(self.end(Cause::Degloved));
782069860236atagen2026-10-10 12:44 UTC50
        }
782069860236atagen2026-10-10 12:44 UTC51
        Ok(ControlFlow::Continue(()))
782069860236atagen2026-10-10 12:44 UTC52
    }
782069860236atagen2026-10-10 12:44 UTC53
782069860236atagen2026-10-10 12:44 UTC54
    /// Everything that can go wrong on a committed turn before the roll.
782069860236atagen2026-10-10 12:44 UTC55
    pub(super) fn hazards(&mut self, intensity: i32) -> Interruption {
b85a0d4536c3atagen2026-10-11 08:50 UTC56
        let burn = self.burn();
3f9091557ec3atagen2026-10-11 11:43 UTC57
        if self.eq <= PREMATURE_EQ - burn
3f9091557ec3atagen2026-10-11 11:43 UTC58
            && self.chance(
3f9091557ec3atagen2026-10-11 11:43 UTC59
                self.decision("rupture", "Does this jelq rupture something?"),
3f9091557ec3atagen2026-10-11 11:43 UTC60
                self.rupture_odds(),
3f9091557ec3atagen2026-10-11 11:43 UTC61
            )
3f9091557ec3atagen2026-10-11 11:43 UTC62
        {
782069860236atagen2026-10-10 12:44 UTC63
            return Err(self.rupture());
782069860236atagen2026-10-10 12:44 UTC64
        }
3f9091557ec3atagen2026-10-11 11:43 UTC65
        if self.floor.has_window()
3f9091557ec3atagen2026-10-11 11:43 UTC66
            && !self.items.blinds
3f9091557ec3atagen2026-10-11 11:43 UTC67
            && self.oracle.chance_not(
3f9091557ec3atagen2026-10-11 11:43 UTC68
                self.decision("window", "Are the curtains still closed?"),
3f9091557ec3atagen2026-10-11 11:43 UTC69
                WINDOW,
3f9091557ec3atagen2026-10-11 11:43 UTC70
            )
3f9091557ec3atagen2026-10-11 11:43 UTC71
        {
782069860236atagen2026-10-10 12:44 UTC72
            self.shame += 30;
782069860236atagen2026-10-10 12:44 UTC73
            self.filmed_at.get_or_insert_with(SystemTime::now);
782069860236atagen2026-10-10 12:44 UTC74
            return Ok(ControlFlow::Break(
782069860236atagen2026-10-10 12:44 UTC75
                Outcome::json(
782069860236atagen2026-10-10 12:44 UTC76
                    StatusCode::CREATED,
782069860236atagen2026-10-10 12:44 UTC77
                    json!({ "detail": "A neighbour was at the window. A recording has been created. It has been shared." }),
782069860236atagen2026-10-10 12:44 UTC78
                )
782069860236atagen2026-10-10 12:44 UTC79
                .header(header::LOCATION, self.path("/footage")),
782069860236atagen2026-10-10 12:44 UTC80
            ));
782069860236atagen2026-10-10 12:44 UTC81
        }
3f9091557ec3atagen2026-10-11 11:43 UTC82
        if self.floor == Floor::ChangingRoom
3f9091557ec3atagen2026-10-11 11:43 UTC83
            && self.chance(
3f9091557ec3atagen2026-10-11 11:43 UTC84
                self.decision("friend", "Does a friend walk in and offer to help?"),
3f9091557ec3atagen2026-10-11 11:43 UTC85
                FRIEND,
3f9091557ec3atagen2026-10-11 11:43 UTC86
            )
3f9091557ec3atagen2026-10-11 11:43 UTC87
        {
782069860236atagen2026-10-10 12:44 UTC88
            self.shame += 1;
782069860236atagen2026-10-10 12:44 UTC89
            return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC90
                StatusCode::USE_PROXY,
782069860236atagen2026-10-10 12:44 UTC91
                "friend",
782069860236atagen2026-10-10 12:44 UTC92
                "A friend has offered to help. Use Proxy is deprecated, and the server has declined on your behalf.",
782069860236atagen2026-10-10 12:44 UTC93
            ));
782069860236atagen2026-10-10 12:44 UTC94
        }
3f9091557ec3atagen2026-10-11 11:43 UTC95
        if self.conditions.peyronie
3f9091557ec3atagen2026-10-11 11:43 UTC96
            && self.chance(
3f9091557ec3atagen2026-10-11 11:43 UTC97
                self.decision(
3f9091557ec3atagen2026-10-11 11:43 UTC98
                    "misdirected",
3f9091557ec3atagen2026-10-11 11:43 UTC99
                    "With the curve, does the stroke land sharply to the left?",
3f9091557ec3atagen2026-10-11 11:43 UTC100
                ),
3f9091557ec3atagen2026-10-11 11:43 UTC101
                CURVE_MISSES,
3f9091557ec3atagen2026-10-11 11:43 UTC102
            )
3f9091557ec3atagen2026-10-11 11:43 UTC103
        {
782069860236atagen2026-10-10 12:44 UTC104
            return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC105
                StatusCode::MISDIRECTED_REQUEST,
782069860236atagen2026-10-10 12:44 UTC106
                "misdirected",
782069860236atagen2026-10-10 12:44 UTC107
                "The technique landed slightly to the left. It was meant for a different server.",
782069860236atagen2026-10-10 12:44 UTC108
            ));
782069860236atagen2026-10-10 12:44 UTC109
        }
782069860236atagen2026-10-10 12:44 UTC110
        if intensity <= 2 && self.eq < 50 {
782069860236atagen2026-10-10 12:44 UTC111
            self.eq = (self.eq - 10).max(0);
782069860236atagen2026-10-10 12:44 UTC112
            return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC113
                StatusCode::REQUEST_TIMEOUT,
782069860236atagen2026-10-10 12:44 UTC114
                "lost-it",
782069860236atagen2026-10-10 12:44 UTC115
                "The request took too long. It was lost.",
782069860236atagen2026-10-10 12:44 UTC116
            )
782069860236atagen2026-10-10 12:44 UTC117
            .header(header::CONNECTION, "close"));
782069860236atagen2026-10-10 12:44 UTC118
        }
139164489032atagen2026-10-11 05:46 UTC119
        if self.eq > PREMATURE_EQ - burn {
3f9091557ec3atagen2026-10-11 11:43 UTC120
            if self.chance(
3f9091557ec3atagen2026-10-11 11:43 UTC121
                self.decision(
3f9091557ec3atagen2026-10-11 11:43 UTC122
                    "rupture",
3f9091557ec3atagen2026-10-11 11:43 UTC123
                    "Right at the edge of finishing, does it rupture instead?",
3f9091557ec3atagen2026-10-11 11:43 UTC124
                ),
3f9091557ec3atagen2026-10-11 11:43 UTC125
                RUPTURE_PREMATURE,
3f9091557ec3atagen2026-10-11 11:43 UTC126
            ) {
782069860236atagen2026-10-10 12:44 UTC127
                return Err(self.rupture());
782069860236atagen2026-10-10 12:44 UTC128
            }
782069860236atagen2026-10-10 12:44 UTC129
            self.shame += 5;
782069860236atagen2026-10-10 12:44 UTC130
            self.next_floor(false);
782069860236atagen2026-10-10 12:44 UTC131
            return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC132
                StatusCode::TOO_EARLY,
782069860236atagen2026-10-10 12:44 UTC133
                "premature",
782069860236atagen2026-10-10 12:44 UTC134
                "The floor has ended early. Everyone involved has agreed not to discuss it.",
782069860236atagen2026-10-10 12:44 UTC135
            ));
782069860236atagen2026-10-10 12:44 UTC136
        }
782069860236atagen2026-10-10 12:44 UTC137
        if self.eq < COLD_EQ {
782069860236atagen2026-10-10 12:44 UTC138
            return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC139
                StatusCode::NOT_FOUND,
782069860236atagen2026-10-10 12:44 UTC140
                "cold",
782069860236atagen2026-10-10 12:44 UTC141
                "The resource exists. It is cold.",
782069860236atagen2026-10-10 12:44 UTC142
            ));
782069860236atagen2026-10-10 12:44 UTC143
        }
782069860236atagen2026-10-10 12:44 UTC144
        Ok(ControlFlow::Continue(()))
782069860236atagen2026-10-10 12:44 UTC145
    }
782069860236atagen2026-10-10 12:44 UTC146
9683736a2c5eatagen2026-10-10 13:15 UTC147
    /// The enemies suspicion summons on floors without Mum.
782069860236atagen2026-10-10 12:44 UTC148
    pub(super) fn floor_enemy(&mut self) -> Result<(), Problem> {
782069860236atagen2026-10-10 12:44 UTC149
        let visits = u32::from(self.suspicion) + 1;
782069860236atagen2026-10-10 12:44 UTC150
        let wanders_in = matches!(self.floor, Floor::Bathroom | Floor::Hotel)
3f9091557ec3atagen2026-10-11 11:43 UTC151
            && self.oracle.ratio_choice(
3f9091557ec3atagen2026-10-11 11:43 UTC152
                self.decision("wanderer", "Does anyone else come through the door?"),
3f9091557ec3atagen2026-10-11 11:43 UTC153
                visits,
3f9091557ec3atagen2026-10-11 11:43 UTC154
                WANDERER_VISIT,
3f9091557ec3atagen2026-10-11 11:43 UTC155
                "No",
3f9091557ec3atagen2026-10-11 11:43 UTC156
                "Yes",
3f9091557ec3atagen2026-10-11 11:43 UTC157
            );
782069860236atagen2026-10-10 12:44 UTC158
        if self.suspicion < SUSPICION_LIMIT && !wanders_in {
782069860236atagen2026-10-10 12:44 UTC159
            return Ok(());
782069860236atagen2026-10-10 12:44 UTC160
        }
782069860236atagen2026-10-10 12:44 UTC161
        match self.floor {
782069860236atagen2026-10-10 12:44 UTC162
            Floor::Bathroom => {
782069860236atagen2026-10-10 12:44 UTC163
                self.suspicion = 0;
782069860236atagen2026-10-10 12:44 UTC164
                self.touch();
782069860236atagen2026-10-10 12:44 UTC165
                if self.items.door_locks > 0 {
782069860236atagen2026-10-10 12:44 UTC166
                    self.items.door_locks -= 1;
7d3fb63208bfatagen2026-10-11 04:38 UTC167
                    self.notices.push("your brother tried the door");
782069860236atagen2026-10-10 12:44 UTC168
                    return Ok(());
782069860236atagen2026-10-10 12:44 UTC169
                }
7d3fb63208bfatagen2026-10-11 04:38 UTC170
                self.here.brother_told = true;
782069860236atagen2026-10-10 12:44 UTC171
                Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC172
                    StatusCode::CONFLICT,
7d3fb63208bfatagen2026-10-11 04:38 UTC173
                    "brother",
7d3fb63208bfatagen2026-10-11 04:38 UTC174
                    "Your brother needs the bathroom. He will tell Mum.",
782069860236atagen2026-10-10 12:44 UTC175
                ))
782069860236atagen2026-10-10 12:44 UTC176
            }
9683736a2c5eatagen2026-10-10 13:15 UTC177
            Floor::ChangingRoom => {
782069860236atagen2026-10-10 12:44 UTC178
                self.suspicion = 0;
782069860236atagen2026-10-10 12:44 UTC179
                self.touch();
058650ec79d6atagen2026-10-12 10:49 UTC180
                if self.girth_respected() {
058650ec79d6atagen2026-10-12 10:49 UTC181
                    self.notices
058650ec79d6atagen2026-10-12 10:49 UTC182
                        .push("the gym staff have seen worse, and they leave it there");
058650ec79d6atagen2026-10-12 10:49 UTC183
                    return Ok(());
058650ec79d6atagen2026-10-12 10:49 UTC184
                }
058650ec79d6atagen2026-10-12 10:49 UTC185
                self.shame += 5;
782069860236atagen2026-10-10 12:44 UTC186
                Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC187
                    StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS,
782069860236atagen2026-10-10 12:44 UTC188
                    "gym-staff",
782069860236atagen2026-10-10 12:44 UTC189
                    "Gym staff have been notified. The server cannot serve this request for legal reasons.",
782069860236atagen2026-10-10 12:44 UTC190
                ))
782069860236atagen2026-10-10 12:44 UTC191
            }
782069860236atagen2026-10-10 12:44 UTC192
            Floor::Hotel => {
782069860236atagen2026-10-10 12:44 UTC193
                self.suspicion = 0;
782069860236atagen2026-10-10 12:44 UTC194
                self.touch();
782069860236atagen2026-10-10 12:44 UTC195
                if self.items.dnd_sign {
d46f9d0b953aatagen2026-10-10 14:41 UTC196
                    self.notices.push("housekeeping read the sign");
782069860236atagen2026-10-10 12:44 UTC197
                    return Ok(());
782069860236atagen2026-10-10 12:44 UTC198
                }
782069860236atagen2026-10-10 12:44 UTC199
                self.shame += 3;
782069860236atagen2026-10-10 12:44 UTC200
                Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC201
                    StatusCode::UNAUTHORIZED,
782069860236atagen2026-10-10 12:44 UTC202
                    "housekeeping",
782069860236atagen2026-10-10 12:44 UTC203
                    "Housekeeping has entered. Housekeeping accepts no excuses.",
782069860236atagen2026-10-10 12:44 UTC204
                )
782069860236atagen2026-10-10 12:44 UTC205
                .header(header::WWW_AUTHENTICATE, "Excuse realm=\"housekeeping\""))
782069860236atagen2026-10-10 12:44 UTC206
            }
782069860236atagen2026-10-10 12:44 UTC207
            _ => Ok(()),
782069860236atagen2026-10-10 12:44 UTC208
        }
782069860236atagen2026-10-10 12:44 UTC209
    }
782069860236atagen2026-10-10 12:44 UTC210
}