atagen

jelq

public

an http based roguelike rpg about jelqing

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