atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
bf5f8cb6f60f6e974991335cdfec63b7874a37bf
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/game/hazards.rsRust232 lines8.0 KB
1
use std::{ops::ControlFlow, time::SystemTime};
2
3
use axum::http::{StatusCode, header};
4
use serde_json::json;
5
6
use super::{
7
    Cause, Floor, Notice, Outcome, Run,
8
    balance::{
9
        BURN_RUPTURE, COLD_DEGLOVING, COLD_EQ, CURVE_MISSES, DANGER_EQ, DEGLOVING,
10
        DEGLOVING_INTENSITY, EQ_BAND, FRIEND, HOT_EQ, LOST_IT_EQ, PREMATURE_EQ, RUPTURE_HOT,
11
        RUPTURE_IN_BAND, RUPTURE_PREMATURE, RUPTURE_WARM, SOFT_INTENSITY, SUSPICION_LIMIT,
12
        WANDERER_VISIT, WINDOW,
13
    },
14
    measure::round3,
15
    techniques::Training,
16
    travel::Exit,
17
};
18
use crate::problem::Problem;
19
20
type Interruption = Result<ControlFlow<Outcome>, Problem>;
21
22
impl Run {
23
    /// Whether EQ is past `line`, with a friction burn dragging every line down.
24
    pub(super) fn hotter_than(&self, line: i32) -> bool {
25
        let burn = if self.body.conditions.friction_burn {
26
            BURN_RUPTURE
27
        } else {
28
            0
29
        };
30
        self.body.eq > line - burn
31
    }
32
33
    /// The rupture tier for any turn that is not a jelq, so never the premature band's 1 in 3.
34
    pub(super) fn rupture_odds(&self) -> u32 {
35
        if !self.hotter_than(DANGER_EQ) {
36
            RUPTURE_IN_BAND
37
        } else if !self.hotter_than(HOT_EQ) {
38
            RUPTURE_WARM
39
        } else {
40
            RUPTURE_HOT
41
        }
42
    }
43
44
    pub(super) fn cold(&self) -> bool {
45
        self.body.eq < COLD_EQ
46
    }
47
48
    pub(super) fn mishaps(
49
        &mut self,
50
        training: Training,
51
        intensity: i32,
52
        cold: bool,
53
    ) -> Interruption {
54
        if cold {
55
            if !self.notices.contains(&Notice::ColdStart) {
56
                self.notices.push(Notice::ColdStart);
57
            }
58
            if self.chance(
59
                self.decision("cold-degloving", "Cold and unprepared, does the skin tear?"),
60
                COLD_DEGLOVING,
61
            ) {
62
                return Err(self.deglove());
63
            }
64
        }
65
        if training == Training::Dry
66
            && intensity >= DEGLOVING_INTENSITY
67
            && self.chance(
68
                self.decision("degloving", "Dry at this intensity, does the skin tear?"),
69
                DEGLOVING,
70
            )
71
        {
72
            return Err(self.deglove());
73
        }
74
        Ok(ControlFlow::Continue(()))
75
    }
76
77
    fn deglove(&mut self) -> Problem {
78
        let lost = self.body.foreskin.take();
79
        let problem = self.end(Cause::Degloved);
80
        match lost {
81
            Some(lost) => problem.with("foreskin", -round3(lost)),
82
            None => problem,
83
        }
84
    }
85
86
    pub(super) fn hazards(&mut self, intensity: i32) -> Interruption {
87
        if !self.hotter_than(PREMATURE_EQ)
88
            && self.chance(
89
                self.decision("rupture", "Does this jelq rupture something?"),
90
                self.rupture_odds(),
91
            )
92
        {
93
            return Err(self.rupture());
94
        }
95
        if self.floor.has_window()
96
            && !self.items.kit.blinds
97
            && self.oracle.chance_not(
98
                self.decision("window", "Are the curtains still closed?"),
99
                WINDOW,
100
            )
101
        {
102
            self.standing.shame += 30;
103
            self.online.filmed_at.get_or_insert_with(SystemTime::now);
104
            return Ok(ControlFlow::Break(
105
                Outcome::json(
106
                    StatusCode::CREATED,
107
                    json!({ "detail": "A neighbour was at the window. A recording has been created. It has been shared." }),
108
                )
109
                .header(header::LOCATION, self.path("/footage")),
110
            ));
111
        }
112
        if self.floor == Floor::ChangingRoom
113
            && self.chance(
114
                self.decision("friend", "Does a friend walk in and offer to help?"),
115
                FRIEND,
116
            )
117
        {
118
            self.standing.shame += 1;
119
            return Err(Problem::new(
120
                StatusCode::USE_PROXY,
121
                "friend",
122
                "A friend has offered to help. Use Proxy is deprecated, and the server has declined on your behalf.",
123
            ));
124
        }
125
        if self.body.conditions.peyronie
126
            && self.chance(
127
                self.decision(
128
                    "misdirected",
129
                    "With the curve, does the stroke land sharply to the left?",
130
                ),
131
                CURVE_MISSES,
132
            )
133
        {
134
            return Err(Problem::new(
135
                StatusCode::MISDIRECTED_REQUEST,
136
                "misdirected",
137
                "The technique landed slightly to the left. It was meant for a different server.",
138
            ));
139
        }
140
        if intensity <= SOFT_INTENSITY && self.body.eq < *EQ_BAND.start() {
141
            self.body.eq = (self.body.eq - LOST_IT_EQ).max(0);
142
            return Err(Problem::new(
143
                StatusCode::REQUEST_TIMEOUT,
144
                "lost-it",
145
                "The request took too long. It was lost.",
146
            )
147
            .header(header::CONNECTION, "close"));
148
        }
149
        if self.hotter_than(PREMATURE_EQ) {
150
            if self.chance(
151
                self.decision(
152
                    "rupture",
153
                    "Right at the edge of finishing, does it rupture instead?",
154
                ),
155
                RUPTURE_PREMATURE,
156
            ) {
157
                return Err(self.rupture());
158
            }
159
            self.standing.shame += 5;
160
            self.next_floor(Exit::Premature);
161
            return Err(Problem::new(
162
                StatusCode::TOO_EARLY,
163
                "premature",
164
                "The floor has ended early. Everyone involved has agreed not to discuss it.",
165
            ));
166
        }
167
        Ok(ControlFlow::Continue(()))
168
    }
169
170
    /// The enemies suspicion summons on floors without Mum.
171
    pub(super) fn floor_enemy(&mut self) -> Result<(), Problem> {
172
        let visits = u32::from(self.standing.suspicion) + 1;
173
        let wanders_in = matches!(self.floor, Floor::Bathroom | Floor::Hotel)
174
            && self.oracle.ratio_choice(
175
                self.decision("wanderer", "Does anyone else come through the door?"),
176
                visits,
177
                WANDERER_VISIT,
178
                "No",
179
                "Yes",
180
            );
181
        if self.standing.suspicion < SUSPICION_LIMIT && !wanders_in {
182
            return Ok(());
183
        }
184
        match self.floor {
185
            Floor::Bathroom => {
186
                self.standing.suspicion = 0;
187
                self.touch();
188
                if self.items.stores.door_locks > 0 {
189
                    self.items.stores.door_locks -= 1;
190
                    self.notices.push(Notice::BrotherTriedTheDoor);
191
                    return Ok(());
192
                }
193
                self.here.brother_told = true;
194
                Err(Problem::new(
195
                    StatusCode::CONFLICT,
196
                    "brother",
197
                    "Your brother needs the bathroom. He will tell Mum.",
198
                ))
199
            }
200
            Floor::ChangingRoom => {
201
                self.standing.suspicion = 0;
202
                self.touch();
203
                if self.girth_respected() {
204
                    self.notices.push(Notice::GymStaffLeaveIt);
205
                    return Ok(());
206
                }
207
                self.standing.shame += 5;
208
                Err(Problem::new(
209
                    StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS,
210
                    "gym-staff",
211
                    "Gym staff have been notified. The server cannot serve this request for legal reasons.",
212
                ))
213
            }
214
            Floor::Hotel => {
215
                self.standing.suspicion = 0;
216
                self.touch();
217
                if self.items.kit.dnd_sign {
218
                    self.notices.push(Notice::HousekeepingReadTheSign);
219
                    return Ok(());
220
                }
221
                self.standing.shame += 3;
222
                Err(Problem::new(
223
                    StatusCode::UNAUTHORIZED,
224
                    "housekeeping",
225
                    "Housekeeping has entered. Housekeeping accepts no excuses.",
226
                )
227
                .header(header::WWW_AUTHENTICATE, "Excuse realm=\"housekeeping\""))
228
            }
229
            _ => Ok(()),
230
        }
231
    }
232
}