atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
e807266c323bcc1fd00f59aa160b3b94d8168beb
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/game/floors.rsRust255 lines9.0 KB
1
use axum::http::{StatusCode, header};
2
use serde_json::{Value, json};
3
4
use super::{
5
    Cause, Elder, Floor, FloorState, Item, Outcome, Run,
6
    balance::{
7
        EQ_BAND, FLOOR_ALLOWANCE, FLOOR_TURNS, FRICTION_FLOOR, GREGS_LOCKER, KEY_IN_DRAWER,
8
        RULER_IN_DRAWER, STARTING_EQ, SUSPICION_LIMIT, ZIP,
9
    },
10
    round2,
11
};
12
use crate::problem::Problem;
13
14
impl Run {
15
    pub(super) fn next_floor(&mut self, paid: bool) {
16
        let Some(next) = self.floor.next() else {
17
            return;
18
        };
19
        let warm = EQ_BAND.contains(&self.eq);
20
        self.floor = next;
21
        self.here = FloorState {
22
            warm_arrival: warm,
23
            ..FloorState::default()
24
        };
25
        self.warmed_up = false;
26
        self.reset_eq(STARTING_EQ);
27
        self.suspicion = 0;
28
        self.shed(FRICTION_FLOOR);
29
        self.mum = None;
30
        if paid {
31
            self.cash += FLOOR_ALLOWANCE;
32
        }
33
        self.touch();
34
    }
35
36
    pub fn advance(&mut self) -> Result<Outcome, Problem> {
37
        match self.floor {
38
            Floor::Clinic => {
39
                return Err(Problem::new(
40
                    StatusCode::CONFLICT,
41
                    "no-further-floor",
42
                    "There is no further floor. There is only the diagnosis.",
43
                ));
44
            }
45
            Floor::Forum if self.elder != Elder::Beaten => {
46
                return Err(Problem::new(
47
                    StatusCode::CONFLICT,
48
                    "elder-unaddressed",
49
                    "The Elder has not been addressed.",
50
                )
51
                .with("elder", self.path("/elder")));
52
            }
53
            floor if floor.timed() && self.here.turns < FLOOR_TURNS => {
54
                return Err(Problem::new(
55
                    StatusCode::TOO_EARLY,
56
                    "floor-not-over",
57
                    format!(
58
                        "The floor is not over. {} turns remain.",
59
                        FLOOR_TURNS - self.here.turns
60
                    ),
61
                ));
62
            }
63
            _ => {}
64
        }
65
        if self.chance(
66
            self.decision(
67
                "zip",
68
                "As the floor ends and you put yourself away, does the zip catch?",
69
            ),
70
            ZIP,
71
        ) {
72
            return Err(self.end(Cause::Zip));
73
        }
74
        let left = self.floor.name();
75
        let reverted = self.here.temp_length;
76
        self.next_floor(true);
77
        let warm = if self.here.warm_arrival {
78
            ", and the warmth came with you"
79
        } else {
80
            ""
81
        };
82
        let mut body = json!({
83
            "detail": format!("You leave {left}. You have arrived at {}{warm}.", self.floor.name()),
84
            "note": self.floor.note(),
85
        });
86
        if reverted > 0.0 {
87
            body["reverted"] = json!(round2(reverted));
88
        }
89
        Ok(
90
            Outcome::json(StatusCode::SEE_OTHER, body)
91
                .header(header::LOCATION, self.path("/floor")),
92
        )
93
    }
94
95
    pub fn floor_view(&self) -> Value {
96
        let mut view = json!({
97
            "name": self.floor.name(),
98
            "note": self.floor.note(),
99
            "turns": self.here.turns,
100
            "turns_required": self.floor.timed().then_some(FLOOR_TURNS),
101
            "exit": { "href": self.path("/floor"), "method": "POST" },
102
        });
103
        match self.floor {
104
            Floor::ChangingRoom => {
105
                view["others"] = json!([
106
                    { "name": "Greg", "self_reported_length": 9.5 },
107
                    { "name": "Dmitri", "self_reported_length": 8.75 },
108
                    { "name": "a man in a towel", "self_reported_length": 11.0 },
109
                ]);
110
            }
111
            Floor::Hotel => view["portal"] = json!(self.path("/portal")),
112
            Floor::Forum => {
113
                view["thread"] = json!(super::FORUM_THREAD);
114
                view["elder"] = json!(self.path("/elder"));
115
            }
116
            Floor::Clinic => view["diagnosis"] = json!(self.path("/diagnosis")),
117
            _ => {}
118
        }
119
        if self.floor.shop_open() {
120
            view["shop"] = json!(self.path("/shop"));
121
        }
122
        view
123
    }
124
125
    fn found(&self, item: Item, detail: &str) -> Outcome {
126
        Outcome::json(StatusCode::FOUND, json!({ "detail": detail })).header(
127
            header::LOCATION,
128
            self.path(&format!("/inventory/{}", item.slug())),
129
        )
130
    }
131
132
    pub fn search_drawer(&mut self) -> Result<Outcome, Problem> {
133
        self.touch();
134
        self.end_kitchen_visit();
135
        if self.items.cage {
136
            return Ok(self.rummage_for_key());
137
        }
138
        let first = !self.here.drawer_searched;
139
        self.here.drawer_searched = true;
140
        let ruler_here = self.floor == Floor::Bedroom
141
            && self.chance(
142
                self.decision("ruler", "Is there a ruler hiding in the drawer?"),
143
                RULER_IN_DRAWER,
144
            );
145
        let locker_open = self.floor == Floor::ChangingRoom
146
            && first
147
            && self.chance(
148
                self.decision("gregs-locker", "Has Greg left his locker open?"),
149
                GREGS_LOCKER,
150
            );
151
        match self.floor {
152
            Floor::Bedroom if first && !self.items.ruler && ruler_here => {
153
                self.items.grant(Item::Ruler);
154
                self.shame += 10;
155
                Ok(self.found(
156
                    Item::Ruler,
157
                    "You now know. This is the first honest measurement of the run.",
158
                ))
159
            }
160
            Floor::Bathroom if first => {
161
                self.items.grant(Item::DoorLock);
162
                self.items.grant(Item::Vaseline);
163
                Ok(self.found(
164
                    Item::DoorLock,
165
                    "A door lock, which works once, and a tub of vaseline behind it.",
166
                ))
167
            }
168
            Floor::ChangingRoom if locker_open => {
169
                self.items.grant(Item::BabyOil);
170
                Ok(self.found(
171
                    Item::BabyOil,
172
                    "Greg has left his locker open. The baby oil is yours now, legally speaking.",
173
                ))
174
            }
175
            Floor::ChangingRoom => {
176
                self.shame += 3;
177
                Err(Problem::new(
178
                    StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS,
179
                    "not-your-locker",
180
                    "That locker belongs to Greg. The server cannot serve this request for legal reasons.",
181
                ))
182
            }
183
            Floor::Nans if first => {
184
                self.items.grant(Item::Cage);
185
                self.shame += 5;
186
                Ok(self.found(
187
                    Item::Cage,
188
                    "A small box. Inside was a cage. It is now on. The key is not in the box.",
189
                ))
190
            }
191
            Floor::Hotel if first => {
192
                self.items.grant(Item::DndSign);
193
                self.items.grant(Item::IcePack);
194
                self.items.grant(Item::IcePack);
195
                Ok(self.found(
196
                    Item::DndSign,
197
                    "A Do Not Disturb sign, and two ice packs from the machine down the hall.",
198
                ))
199
            }
200
            Floor::Forum if first => {
201
                self.items.grant(Item::Spreadsheet);
202
                Ok(self.found(
203
                    Item::Spreadsheet,
204
                    "Your own spreadsheet. Every line is flat.",
205
                ))
206
            }
207
            Floor::Clinic => Err(Problem::new(
208
                StatusCode::FORBIDDEN,
209
                "doctors-drawer",
210
                "That is the doctor's drawer.",
211
            )),
212
            _ => Ok(Outcome::empty(StatusCode::NO_CONTENT)),
213
        }
214
    }
215
216
    pub fn accept_terms(&mut self) -> Result<Outcome, Problem> {
217
        if self.floor != Floor::Hotel {
218
            return Err(Problem::new(
219
                StatusCode::NOT_FOUND,
220
                "no-portal",
221
                "There is no captive portal here. There is only the regular kind of captivity.",
222
            ));
223
        }
224
        self.here.wifi_accepted = true;
225
        self.touch();
226
        Ok(Outcome::json(
227
            StatusCode::SEE_OTHER,
228
            json!({ "detail": "Connected. The hotel does not want to know." }),
229
        )
230
        .header(header::LOCATION, self.path("")))
231
    }
232
233
    /// Searching while caged is noisy, and the key only turns up some of the time.
234
    fn rummage_for_key(&mut self) -> Outcome {
235
        let was_below = self.suspicion < SUSPICION_LIMIT;
236
        self.suspicion = (self.suspicion + 1).min(SUSPICION_LIMIT);
237
        self.maybe_summon_mum(was_below);
238
        if !self.chance(
239
            self.decision("key", "Does the drawer finally give up the cage key?"),
240
            KEY_IN_DRAWER,
241
        ) {
242
            return Outcome::empty(StatusCode::NO_CONTENT);
243
        }
244
        self.items.grant(Item::CageKey);
245
        Outcome::json(
246
            StatusCode::OK,
247
            json!({ "detail": "The key was in the drawer. The cage is off." }),
248
        )
249
    }
250
251
    /// Whether searching the drawer on this floor would turn anything up.
252
    pub fn drawer_worth_searching(&self) -> bool {
253
        !self.here.drawer_searched || self.items.cage
254
    }
255
}