atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
c4d70621d377d45e3d418ac9197443c1b5d36399
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git

Blame: src/game/floors.rs

782069860236atagen2026-10-10 12:44 UTC1
use axum::http::{StatusCode, header};
782069860236atagen2026-10-10 12:44 UTC2
use serde_json::{Value, json};
782069860236atagen2026-10-10 12:44 UTC3
782069860236atagen2026-10-10 12:44 UTC4
use super::{
782069860236atagen2026-10-10 12:44 UTC5
    Cause, Elder, Floor, FloorState, Item, Outcome, Run,
0d604ee39751atagen2026-10-10 14:15 UTC6
    balance::{
a6caa2f99786atagen2026-10-12 10:38 UTC7
        DRAWER_JAMS, EQ_BAND, FLOOR_ALLOWANCE, FLOOR_TURNS, FRICTION_FLOOR, GREGS_LOCKER,
058650ec79d6atagen2026-10-12 10:49 UTC8
        KEY_IN_DRAWER, LOCKER_ROOM_BAR, LOCKER_ROOM_SHAME, RULER_IN_DRAWER, STARTING_EQ,
058650ec79d6atagen2026-10-12 10:49 UTC9
        SUSPICION_LIMIT, ZIP,
0d604ee39751atagen2026-10-10 14:15 UTC10
    },
782069860236atagen2026-10-10 12:44 UTC11
    round2,
782069860236atagen2026-10-10 12:44 UTC12
};
782069860236atagen2026-10-10 12:44 UTC13
use crate::problem::Problem;
782069860236atagen2026-10-10 12:44 UTC14
782069860236atagen2026-10-10 12:44 UTC15
impl Run {
782069860236atagen2026-10-10 12:44 UTC16
    pub(super) fn next_floor(&mut self, paid: bool) {
782069860236atagen2026-10-10 12:44 UTC17
        let Some(next) = self.floor.next() else {
782069860236atagen2026-10-10 12:44 UTC18
            return;
782069860236atagen2026-10-10 12:44 UTC19
        };
4347977b8192atagen2026-10-11 04:56 UTC20
        let warm = EQ_BAND.contains(&self.eq);
782069860236atagen2026-10-10 12:44 UTC21
        self.floor = next;
4347977b8192atagen2026-10-11 04:56 UTC22
        self.here = FloorState {
4347977b8192atagen2026-10-11 04:56 UTC23
            warm_arrival: warm,
4347977b8192atagen2026-10-11 04:56 UTC24
            ..FloorState::default()
4347977b8192atagen2026-10-11 04:56 UTC25
        };
782069860236atagen2026-10-10 12:44 UTC26
        self.warmed_up = false;
14de3e1e8dc2atagen2026-10-11 06:19 UTC27
        self.reset_eq(STARTING_EQ);
782069860236atagen2026-10-10 12:44 UTC28
        self.suspicion = 0;
5dfebcc0ac43atagen2026-10-11 05:28 UTC29
        self.shed(FRICTION_FLOOR);
9683736a2c5eatagen2026-10-10 13:15 UTC30
        self.mum = None;
058650ec79d6atagen2026-10-12 10:49 UTC31
        if self.floor == Floor::ChangingRoom && !self.holds_own_in_the_locker_room() {
058650ec79d6atagen2026-10-12 10:49 UTC32
            self.shame += LOCKER_ROOM_SHAME;
058650ec79d6atagen2026-10-12 10:49 UTC33
            self.notices
058650ec79d6atagen2026-10-12 10:49 UTC34
                .push("Kev is 8 inches, he says. Nobody asked you, which is worse.");
058650ec79d6atagen2026-10-12 10:49 UTC35
        }
782069860236atagen2026-10-10 12:44 UTC36
        if paid {
782069860236atagen2026-10-10 12:44 UTC37
            self.cash += FLOOR_ALLOWANCE;
782069860236atagen2026-10-10 12:44 UTC38
        }
782069860236atagen2026-10-10 12:44 UTC39
        self.touch();
782069860236atagen2026-10-10 12:44 UTC40
    }
782069860236atagen2026-10-10 12:44 UTC41
782069860236atagen2026-10-10 12:44 UTC42
    pub fn advance(&mut self) -> Result<Outcome, Problem> {
782069860236atagen2026-10-10 12:44 UTC43
        match self.floor {
782069860236atagen2026-10-10 12:44 UTC44
            Floor::Clinic => {
782069860236atagen2026-10-10 12:44 UTC45
                return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC46
                    StatusCode::CONFLICT,
782069860236atagen2026-10-10 12:44 UTC47
                    "no-further-floor",
782069860236atagen2026-10-10 12:44 UTC48
                    "There is no further floor. There is only the diagnosis.",
782069860236atagen2026-10-10 12:44 UTC49
                ));
782069860236atagen2026-10-10 12:44 UTC50
            }
782069860236atagen2026-10-10 12:44 UTC51
            Floor::Forum if self.elder != Elder::Beaten => {
782069860236atagen2026-10-10 12:44 UTC52
                return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC53
                    StatusCode::CONFLICT,
782069860236atagen2026-10-10 12:44 UTC54
                    "elder-unaddressed",
782069860236atagen2026-10-10 12:44 UTC55
                    "The Elder has not been addressed.",
782069860236atagen2026-10-10 12:44 UTC56
                )
782069860236atagen2026-10-10 12:44 UTC57
                .with("elder", self.path("/elder")));
782069860236atagen2026-10-10 12:44 UTC58
            }
782069860236atagen2026-10-10 12:44 UTC59
            floor if floor.timed() && self.here.turns < FLOOR_TURNS => {
782069860236atagen2026-10-10 12:44 UTC60
                return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC61
                    StatusCode::TOO_EARLY,
782069860236atagen2026-10-10 12:44 UTC62
                    "floor-not-over",
782069860236atagen2026-10-10 12:44 UTC63
                    format!(
782069860236atagen2026-10-10 12:44 UTC64
                        "The floor is not over. {} turns remain.",
782069860236atagen2026-10-10 12:44 UTC65
                        FLOOR_TURNS - self.here.turns
782069860236atagen2026-10-10 12:44 UTC66
                    ),
782069860236atagen2026-10-10 12:44 UTC67
                ));
782069860236atagen2026-10-10 12:44 UTC68
            }
782069860236atagen2026-10-10 12:44 UTC69
            _ => {}
782069860236atagen2026-10-10 12:44 UTC70
        }
3f9091557ec3atagen2026-10-11 11:43 UTC71
        if self.chance(
3f9091557ec3atagen2026-10-11 11:43 UTC72
            self.decision(
3f9091557ec3atagen2026-10-11 11:43 UTC73
                "zip",
3f9091557ec3atagen2026-10-11 11:43 UTC74
                "As the floor ends and you put yourself away, does the zip catch?",
3f9091557ec3atagen2026-10-11 11:43 UTC75
            ),
3f9091557ec3atagen2026-10-11 11:43 UTC76
            ZIP,
3f9091557ec3atagen2026-10-11 11:43 UTC77
        ) {
9683736a2c5eatagen2026-10-10 13:15 UTC78
            return Err(self.end(Cause::Zip));
782069860236atagen2026-10-10 12:44 UTC79
        }
7d3fb63208bfatagen2026-10-11 04:38 UTC80
        let left = self.floor.name();
782069860236atagen2026-10-10 12:44 UTC81
        let reverted = self.here.temp_length;
782069860236atagen2026-10-10 12:44 UTC82
        self.next_floor(true);
4347977b8192atagen2026-10-11 04:56 UTC83
        let warm = if self.here.warm_arrival {
4347977b8192atagen2026-10-11 04:56 UTC84
            ", and the warmth came with you"
4347977b8192atagen2026-10-11 04:56 UTC85
        } else {
4347977b8192atagen2026-10-11 04:56 UTC86
            ""
4347977b8192atagen2026-10-11 04:56 UTC87
        };
782069860236atagen2026-10-10 12:44 UTC88
        let mut body = json!({
4347977b8192atagen2026-10-11 04:56 UTC89
            "detail": format!("You leave {left}. You have arrived at {}{warm}.", self.floor.name()),
782069860236atagen2026-10-10 12:44 UTC90
            "note": self.floor.note(),
782069860236atagen2026-10-10 12:44 UTC91
        });
782069860236atagen2026-10-10 12:44 UTC92
        if reverted > 0.0 {
782069860236atagen2026-10-10 12:44 UTC93
            body["reverted"] = json!(round2(reverted));
782069860236atagen2026-10-10 12:44 UTC94
        }
782069860236atagen2026-10-10 12:44 UTC95
        Ok(
782069860236atagen2026-10-10 12:44 UTC96
            Outcome::json(StatusCode::SEE_OTHER, body)
782069860236atagen2026-10-10 12:44 UTC97
                .header(header::LOCATION, self.path("/floor")),
782069860236atagen2026-10-10 12:44 UTC98
        )
782069860236atagen2026-10-10 12:44 UTC99
    }
782069860236atagen2026-10-10 12:44 UTC100
782069860236atagen2026-10-10 12:44 UTC101
    pub fn floor_view(&self) -> Value {
782069860236atagen2026-10-10 12:44 UTC102
        let mut view = json!({
782069860236atagen2026-10-10 12:44 UTC103
            "name": self.floor.name(),
782069860236atagen2026-10-10 12:44 UTC104
            "note": self.floor.note(),
782069860236atagen2026-10-10 12:44 UTC105
            "turns": self.here.turns,
782069860236atagen2026-10-10 12:44 UTC106
            "turns_required": self.floor.timed().then_some(FLOOR_TURNS),
782069860236atagen2026-10-10 12:44 UTC107
            "exit": { "href": self.path("/floor"), "method": "POST" },
782069860236atagen2026-10-10 12:44 UTC108
        });
782069860236atagen2026-10-10 12:44 UTC109
        match self.floor {
9683736a2c5eatagen2026-10-10 13:15 UTC110
            Floor::ChangingRoom => {
782069860236atagen2026-10-10 12:44 UTC111
                view["others"] = json!([
782069860236atagen2026-10-10 12:44 UTC112
                    { "name": "Greg", "self_reported_length": 9.5 },
782069860236atagen2026-10-10 12:44 UTC113
                    { "name": "Dmitri", "self_reported_length": 8.75 },
782069860236atagen2026-10-10 12:44 UTC114
                    { "name": "a man in a towel", "self_reported_length": 11.0 },
058650ec79d6atagen2026-10-12 10:49 UTC115
                    { "name": "Kev", "self_reported_length": LOCKER_ROOM_BAR },
782069860236atagen2026-10-10 12:44 UTC116
                ]);
782069860236atagen2026-10-10 12:44 UTC117
            }
782069860236atagen2026-10-10 12:44 UTC118
            Floor::Hotel => view["portal"] = json!(self.path("/portal")),
782069860236atagen2026-10-10 12:44 UTC119
            Floor::Forum => {
782069860236atagen2026-10-10 12:44 UTC120
                view["thread"] = json!(super::FORUM_THREAD);
782069860236atagen2026-10-10 12:44 UTC121
                view["elder"] = json!(self.path("/elder"));
782069860236atagen2026-10-10 12:44 UTC122
            }
782069860236atagen2026-10-10 12:44 UTC123
            Floor::Clinic => view["diagnosis"] = json!(self.path("/diagnosis")),
782069860236atagen2026-10-10 12:44 UTC124
            _ => {}
782069860236atagen2026-10-10 12:44 UTC125
        }
782069860236atagen2026-10-10 12:44 UTC126
        if self.floor.shop_open() {
782069860236atagen2026-10-10 12:44 UTC127
            view["shop"] = json!(self.path("/shop"));
782069860236atagen2026-10-10 12:44 UTC128
        }
782069860236atagen2026-10-10 12:44 UTC129
        view
782069860236atagen2026-10-10 12:44 UTC130
    }
782069860236atagen2026-10-10 12:44 UTC131
782069860236atagen2026-10-10 12:44 UTC132
    fn found(&self, item: Item, detail: &str) -> Outcome {
782069860236atagen2026-10-10 12:44 UTC133
        Outcome::json(StatusCode::FOUND, json!({ "detail": detail })).header(
782069860236atagen2026-10-10 12:44 UTC134
            header::LOCATION,
782069860236atagen2026-10-10 12:44 UTC135
            self.path(&format!("/inventory/{}", item.slug())),
782069860236atagen2026-10-10 12:44 UTC136
        )
782069860236atagen2026-10-10 12:44 UTC137
    }
782069860236atagen2026-10-10 12:44 UTC138
782069860236atagen2026-10-10 12:44 UTC139
    pub fn search_drawer(&mut self) -> Result<Outcome, Problem> {
782069860236atagen2026-10-10 12:44 UTC140
        self.touch();
e807266c323batagen2026-10-12 09:46 UTC141
        self.end_kitchen_visit();
0d604ee39751atagen2026-10-10 14:15 UTC142
        if self.items.cage {
0d604ee39751atagen2026-10-10 14:15 UTC143
            return Ok(self.rummage_for_key());
0d604ee39751atagen2026-10-10 14:15 UTC144
        }
e807266c323batagen2026-10-12 09:46 UTC145
        let first = !self.here.drawer_searched;
e807266c323batagen2026-10-12 09:46 UTC146
        self.here.drawer_searched = true;
3f9091557ec3atagen2026-10-11 11:43 UTC147
        let ruler_here = self.floor == Floor::Bedroom
3f9091557ec3atagen2026-10-11 11:43 UTC148
            && self.chance(
3f9091557ec3atagen2026-10-11 11:43 UTC149
                self.decision("ruler", "Is there a ruler hiding in the drawer?"),
3f9091557ec3atagen2026-10-11 11:43 UTC150
                RULER_IN_DRAWER,
3f9091557ec3atagen2026-10-11 11:43 UTC151
            );
a6caa2f99786atagen2026-10-12 10:38 UTC152
        let vaseline_here = self.floor == Floor::Bathroom
a6caa2f99786atagen2026-10-12 10:38 UTC153
            && first
a6caa2f99786atagen2026-10-12 10:38 UTC154
            && self.chance(
a6caa2f99786atagen2026-10-12 10:38 UTC155
                self.decision(
a6caa2f99786atagen2026-10-12 10:38 UTC156
                    "bathroom-vaseline",
a6caa2f99786atagen2026-10-12 10:38 UTC157
                    "Is it the vaseline behind the door lock, rather than the lotion?",
a6caa2f99786atagen2026-10-12 10:38 UTC158
                ),
a6caa2f99786atagen2026-10-12 10:38 UTC159
                2,
a6caa2f99786atagen2026-10-12 10:38 UTC160
            );
e807266c323batagen2026-10-12 09:46 UTC161
        let locker_open = self.floor == Floor::ChangingRoom
e807266c323batagen2026-10-12 09:46 UTC162
            && first
e807266c323batagen2026-10-12 09:46 UTC163
            && self.chance(
e807266c323batagen2026-10-12 09:46 UTC164
                self.decision("gregs-locker", "Has Greg left his locker open?"),
e807266c323batagen2026-10-12 09:46 UTC165
                GREGS_LOCKER,
e807266c323batagen2026-10-12 09:46 UTC166
            );
782069860236atagen2026-10-10 12:44 UTC167
        match self.floor {
ad00e0e051a1atagen2026-10-10 14:00 UTC168
            Floor::Bedroom if first && !self.items.ruler && ruler_here => {
782069860236atagen2026-10-10 12:44 UTC169
                self.items.grant(Item::Ruler);
782069860236atagen2026-10-10 12:44 UTC170
                self.shame += 10;
782069860236atagen2026-10-10 12:44 UTC171
                Ok(self.found(
782069860236atagen2026-10-10 12:44 UTC172
                    Item::Ruler,
782069860236atagen2026-10-10 12:44 UTC173
                    "You now know. This is the first honest measurement of the run.",
782069860236atagen2026-10-10 12:44 UTC174
                ))
782069860236atagen2026-10-10 12:44 UTC175
            }
782069860236atagen2026-10-10 12:44 UTC176
            Floor::Bathroom if first => {
782069860236atagen2026-10-10 12:44 UTC177
                self.items.grant(Item::DoorLock);
a6caa2f99786atagen2026-10-12 10:38 UTC178
                let detail = if vaseline_here {
a6caa2f99786atagen2026-10-12 10:38 UTC179
                    self.items.grant(Item::Vaseline);
a6caa2f99786atagen2026-10-12 10:38 UTC180
                    "A door lock, which works once, and a tub of vaseline behind it."
a6caa2f99786atagen2026-10-12 10:38 UTC181
                } else {
a6caa2f99786atagen2026-10-12 10:38 UTC182
                    self.items.grant(Item::Lotion);
a6caa2f99786atagen2026-10-12 10:38 UTC183
                    "A door lock, which works once, and a bottle of lotion behind it."
a6caa2f99786atagen2026-10-12 10:38 UTC184
                };
a6caa2f99786atagen2026-10-12 10:38 UTC185
                Ok(self.found(Item::DoorLock, detail))
e807266c323batagen2026-10-12 09:46 UTC186
            }
e807266c323batagen2026-10-12 09:46 UTC187
            Floor::ChangingRoom if locker_open => {
e807266c323batagen2026-10-12 09:46 UTC188
                self.items.grant(Item::BabyOil);
e807266c323batagen2026-10-12 09:46 UTC189
                Ok(self.found(
e807266c323batagen2026-10-12 09:46 UTC190
                    Item::BabyOil,
e807266c323batagen2026-10-12 09:46 UTC191
                    "Greg has left his locker open. The baby oil is yours now, legally speaking.",
e807266c323batagen2026-10-12 09:46 UTC192
                ))
782069860236atagen2026-10-10 12:44 UTC193
            }
9683736a2c5eatagen2026-10-10 13:15 UTC194
            Floor::ChangingRoom => {
782069860236atagen2026-10-10 12:44 UTC195
                self.shame += 3;
782069860236atagen2026-10-10 12:44 UTC196
                Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC197
                    StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS,
782069860236atagen2026-10-10 12:44 UTC198
                    "not-your-locker",
782069860236atagen2026-10-10 12:44 UTC199
                    "That locker belongs to Greg. The server cannot serve this request for legal reasons.",
782069860236atagen2026-10-10 12:44 UTC200
                ))
782069860236atagen2026-10-10 12:44 UTC201
            }
9683736a2c5eatagen2026-10-10 13:15 UTC202
            Floor::Nans if first => {
782069860236atagen2026-10-10 12:44 UTC203
                self.items.grant(Item::Cage);
782069860236atagen2026-10-10 12:44 UTC204
                self.shame += 5;
782069860236atagen2026-10-10 12:44 UTC205
                Ok(self.found(
782069860236atagen2026-10-10 12:44 UTC206
                    Item::Cage,
782069860236atagen2026-10-10 12:44 UTC207
                    "A small box. Inside was a cage. It is now on. The key is not in the box.",
782069860236atagen2026-10-10 12:44 UTC208
                ))
782069860236atagen2026-10-10 12:44 UTC209
            }
782069860236atagen2026-10-10 12:44 UTC210
            Floor::Hotel if first => {
782069860236atagen2026-10-10 12:44 UTC211
                self.items.grant(Item::DndSign);
782069860236atagen2026-10-10 12:44 UTC212
                self.items.grant(Item::IcePack);
782069860236atagen2026-10-10 12:44 UTC213
                self.items.grant(Item::IcePack);
a6caa2f99786atagen2026-10-12 10:38 UTC214
                self.items.grant(Item::Lotion);
782069860236atagen2026-10-10 12:44 UTC215
                Ok(self.found(
782069860236atagen2026-10-10 12:44 UTC216
                    Item::DndSign,
a6caa2f99786atagen2026-10-12 10:38 UTC217
                    "A Do Not Disturb sign, two ice packs from the machine down the hall, and a complimentary lotion.",
782069860236atagen2026-10-10 12:44 UTC218
                ))
782069860236atagen2026-10-10 12:44 UTC219
            }
782069860236atagen2026-10-10 12:44 UTC220
            Floor::Forum if first => {
782069860236atagen2026-10-10 12:44 UTC221
                self.items.grant(Item::Spreadsheet);
782069860236atagen2026-10-10 12:44 UTC222
                Ok(self.found(
782069860236atagen2026-10-10 12:44 UTC223
                    Item::Spreadsheet,
782069860236atagen2026-10-10 12:44 UTC224
                    "Your own spreadsheet. Every line is flat.",
782069860236atagen2026-10-10 12:44 UTC225
                ))
782069860236atagen2026-10-10 12:44 UTC226
            }
782069860236atagen2026-10-10 12:44 UTC227
            Floor::Clinic => Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC228
                StatusCode::FORBIDDEN,
782069860236atagen2026-10-10 12:44 UTC229
                "doctors-drawer",
782069860236atagen2026-10-10 12:44 UTC230
                "That is the doctor's drawer.",
782069860236atagen2026-10-10 12:44 UTC231
            )),
782069860236atagen2026-10-10 12:44 UTC232
            _ => Ok(Outcome::empty(StatusCode::NO_CONTENT)),
782069860236atagen2026-10-10 12:44 UTC233
        }
782069860236atagen2026-10-10 12:44 UTC234
    }
782069860236atagen2026-10-10 12:44 UTC235
782069860236atagen2026-10-10 12:44 UTC236
    pub fn accept_terms(&mut self) -> Result<Outcome, Problem> {
782069860236atagen2026-10-10 12:44 UTC237
        if self.floor != Floor::Hotel {
782069860236atagen2026-10-10 12:44 UTC238
            return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC239
                StatusCode::NOT_FOUND,
782069860236atagen2026-10-10 12:44 UTC240
                "no-portal",
782069860236atagen2026-10-10 12:44 UTC241
                "There is no captive portal here. There is only the regular kind of captivity.",
782069860236atagen2026-10-10 12:44 UTC242
            ));
782069860236atagen2026-10-10 12:44 UTC243
        }
782069860236atagen2026-10-10 12:44 UTC244
        self.here.wifi_accepted = true;
782069860236atagen2026-10-10 12:44 UTC245
        self.touch();
782069860236atagen2026-10-10 12:44 UTC246
        Ok(Outcome::json(
782069860236atagen2026-10-10 12:44 UTC247
            StatusCode::SEE_OTHER,
782069860236atagen2026-10-10 12:44 UTC248
            json!({ "detail": "Connected. The hotel does not want to know." }),
782069860236atagen2026-10-10 12:44 UTC249
        )
782069860236atagen2026-10-10 12:44 UTC250
        .header(header::LOCATION, self.path("")))
782069860236atagen2026-10-10 12:44 UTC251
    }
d604371299fbatagen2026-10-10 13:54 UTC252
a6caa2f99786atagen2026-10-12 10:38 UTC253
    /// Searching while caged is noisy, costs the floor turn, and the key only turns up some of
a6caa2f99786atagen2026-10-12 10:38 UTC254
    /// the time. A jammed drawer takes a whole turn to work loose.
0d604ee39751atagen2026-10-10 14:15 UTC255
    fn rummage_for_key(&mut self) -> Outcome {
a6caa2f99786atagen2026-10-12 10:38 UTC256
        self.turn += 1;
a6caa2f99786atagen2026-10-12 10:38 UTC257
        self.here.turns += 1;
a6caa2f99786atagen2026-10-12 10:38 UTC258
        self.realise();
0d604ee39751atagen2026-10-10 14:15 UTC259
        let was_below = self.suspicion < SUSPICION_LIMIT;
0d604ee39751atagen2026-10-10 14:15 UTC260
        self.suspicion = (self.suspicion + 1).min(SUSPICION_LIMIT);
0d604ee39751atagen2026-10-10 14:15 UTC261
        self.maybe_summon_mum(was_below);
a6caa2f99786atagen2026-10-12 10:38 UTC262
        if self.here.drawer_jammed {
a6caa2f99786atagen2026-10-12 10:38 UTC263
            self.here.drawer_jammed = false;
a6caa2f99786atagen2026-10-12 10:38 UTC264
            self.notices
a6caa2f99786atagen2026-10-12 10:38 UTC265
                .push("The drawer comes loose. That took a while.");
a6caa2f99786atagen2026-10-12 10:38 UTC266
            return Outcome::empty(StatusCode::NO_CONTENT);
a6caa2f99786atagen2026-10-12 10:38 UTC267
        }
3f9091557ec3atagen2026-10-11 11:43 UTC268
        if !self.chance(
3f9091557ec3atagen2026-10-11 11:43 UTC269
            self.decision("key", "Does the drawer finally give up the cage key?"),
3f9091557ec3atagen2026-10-11 11:43 UTC270
            KEY_IN_DRAWER,
3f9091557ec3atagen2026-10-11 11:43 UTC271
        ) {
a6caa2f99786atagen2026-10-12 10:38 UTC272
            if self.chance(
a6caa2f99786atagen2026-10-12 10:38 UTC273
                self.decision("drawer-jams", "Has the drawer jammed on its runners?"),
a6caa2f99786atagen2026-10-12 10:38 UTC274
                DRAWER_JAMS,
a6caa2f99786atagen2026-10-12 10:38 UTC275
            ) {
a6caa2f99786atagen2026-10-12 10:38 UTC276
                self.here.drawer_jammed = true;
a6caa2f99786atagen2026-10-12 10:38 UTC277
                self.notices.push("The drawer has jammed.");
a6caa2f99786atagen2026-10-12 10:38 UTC278
            }
0d604ee39751atagen2026-10-10 14:15 UTC279
            return Outcome::empty(StatusCode::NO_CONTENT);
0d604ee39751atagen2026-10-10 14:15 UTC280
        }
0d604ee39751atagen2026-10-10 14:15 UTC281
        self.items.grant(Item::CageKey);
0d604ee39751atagen2026-10-10 14:15 UTC282
        Outcome::json(
0d604ee39751atagen2026-10-10 14:15 UTC283
            StatusCode::OK,
0d604ee39751atagen2026-10-10 14:15 UTC284
            json!({ "detail": "The key was in the drawer. The cage is off." }),
0d604ee39751atagen2026-10-10 14:15 UTC285
        )
0d604ee39751atagen2026-10-10 14:15 UTC286
    }
0d604ee39751atagen2026-10-10 14:15 UTC287
d604371299fbatagen2026-10-10 13:54 UTC288
    /// Whether searching the drawer on this floor would turn anything up.
d604371299fbatagen2026-10-10 13:54 UTC289
    pub fn drawer_worth_searching(&self) -> bool {
e807266c323batagen2026-10-12 09:46 UTC290
        !self.here.drawer_searched || self.items.cage
d604371299fbatagen2026-10-10 13:54 UTC291
    }
782069860236atagen2026-10-10 12:44 UTC292
}