atagen

jelq

public

an http based roguelike rpg about jelqing

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