atagen

jelq

public

an http based roguelike rpg about jelqing

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