atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
4347977b819220280d64f9e914967717329b830a
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/game/floors.rsRust223 lines7.8 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, KEY_IN_DRAWER, RULER_IN_DRAWER, STARTING_EQ,
8
        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.eq = STARTING_EQ;
27
        self.suspicion = 0;
28
        self.mum = None;
29
        if paid {
30
            self.cash += FLOOR_ALLOWANCE;
31
        }
32
        self.touch();
33
    }
34
35
    pub fn advance(&mut self) -> Result<Outcome, Problem> {
36
        match self.floor {
37
            Floor::Clinic => {
38
                return Err(Problem::new(
39
                    StatusCode::CONFLICT,
40
                    "no-further-floor",
41
                    "There is no further floor. There is only the diagnosis.",
42
                ));
43
            }
44
            Floor::Forum if self.elder != Elder::Beaten => {
45
                return Err(Problem::new(
46
                    StatusCode::CONFLICT,
47
                    "elder-unaddressed",
48
                    "The Elder has not been addressed.",
49
                )
50
                .with("elder", self.path("/elder")));
51
            }
52
            floor if floor.timed() && self.here.turns < FLOOR_TURNS => {
53
                return Err(Problem::new(
54
                    StatusCode::TOO_EARLY,
55
                    "floor-not-over",
56
                    format!(
57
                        "The floor is not over. {} turns remain.",
58
                        FLOOR_TURNS - self.here.turns
59
                    ),
60
                ));
61
            }
62
            _ => {}
63
        }
64
        if self.chance(ZIP) {
65
            return Err(self.end(Cause::Zip));
66
        }
67
        let left = self.floor.name();
68
        let reverted = self.here.temp_length;
69
        self.next_floor(true);
70
        let warm = if self.here.warm_arrival {
71
            ", and the warmth came with you"
72
        } else {
73
            ""
74
        };
75
        let mut body = json!({
76
            "detail": format!("You leave {left}. You have arrived at {}{warm}.", self.floor.name()),
77
            "note": self.floor.note(),
78
        });
79
        if reverted > 0.0 {
80
            body["reverted"] = json!(round2(reverted));
81
        }
82
        Ok(
83
            Outcome::json(StatusCode::SEE_OTHER, body)
84
                .header(header::LOCATION, self.path("/floor")),
85
        )
86
    }
87
88
    pub fn floor_view(&self) -> Value {
89
        let mut view = json!({
90
            "name": self.floor.name(),
91
            "note": self.floor.note(),
92
            "turns": self.here.turns,
93
            "turns_required": self.floor.timed().then_some(FLOOR_TURNS),
94
            "exit": { "href": self.path("/floor"), "method": "POST" },
95
        });
96
        match self.floor {
97
            Floor::ChangingRoom => {
98
                view["others"] = json!([
99
                    { "name": "Greg", "self_reported_length": 9.5 },
100
                    { "name": "Dmitri", "self_reported_length": 8.75 },
101
                    { "name": "a man in a towel", "self_reported_length": 11.0 },
102
                ]);
103
            }
104
            Floor::Hotel => view["portal"] = json!(self.path("/portal")),
105
            Floor::Forum => {
106
                view["thread"] = json!(super::FORUM_THREAD);
107
                view["elder"] = json!(self.path("/elder"));
108
            }
109
            Floor::Clinic => view["diagnosis"] = json!(self.path("/diagnosis")),
110
            _ => {}
111
        }
112
        if self.floor.shop_open() {
113
            view["shop"] = json!(self.path("/shop"));
114
        }
115
        view
116
    }
117
118
    fn found(&self, item: Item, detail: &str) -> Outcome {
119
        Outcome::json(StatusCode::FOUND, json!({ "detail": detail })).header(
120
            header::LOCATION,
121
            self.path(&format!("/inventory/{}", item.slug())),
122
        )
123
    }
124
125
    pub fn search_drawer(&mut self) -> Result<Outcome, Problem> {
126
        self.here.drawer_searches += 1;
127
        self.touch();
128
        let first = self.here.drawer_searches == 1;
129
        if self.items.cage {
130
            return Ok(self.rummage_for_key());
131
        }
132
        let ruler_here = self.floor == Floor::Bedroom && self.chance(RULER_IN_DRAWER);
133
        match self.floor {
134
            Floor::Bedroom if first && !self.items.ruler && ruler_here => {
135
                self.items.grant(Item::Ruler);
136
                self.shame += 10;
137
                Ok(self.found(
138
                    Item::Ruler,
139
                    "You now know. This is the first honest measurement of the run.",
140
                ))
141
            }
142
            Floor::Bathroom if first => {
143
                self.items.grant(Item::DoorLock);
144
                Ok(self.found(Item::DoorLock, "A door lock. It works once."))
145
            }
146
            Floor::ChangingRoom => {
147
                self.shame += 3;
148
                Err(Problem::new(
149
                    StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS,
150
                    "not-your-locker",
151
                    "That locker belongs to Greg. The server cannot serve this request for legal reasons.",
152
                ))
153
            }
154
            Floor::Nans if first => {
155
                self.items.grant(Item::Cage);
156
                self.shame += 5;
157
                Ok(self.found(
158
                    Item::Cage,
159
                    "A small box. Inside was a cage. It is now on. The key is not in the box.",
160
                ))
161
            }
162
            Floor::Hotel if first => {
163
                self.items.grant(Item::DndSign);
164
                self.items.grant(Item::IcePack);
165
                self.items.grant(Item::IcePack);
166
                Ok(self.found(
167
                    Item::DndSign,
168
                    "A Do Not Disturb sign, and two ice packs from the machine down the hall.",
169
                ))
170
            }
171
            Floor::Forum if first => {
172
                self.items.grant(Item::Spreadsheet);
173
                Ok(self.found(
174
                    Item::Spreadsheet,
175
                    "Your own spreadsheet. Every line is flat.",
176
                ))
177
            }
178
            Floor::Clinic => Err(Problem::new(
179
                StatusCode::FORBIDDEN,
180
                "doctors-drawer",
181
                "That is the doctor's drawer.",
182
            )),
183
            _ => Ok(Outcome::empty(StatusCode::NO_CONTENT)),
184
        }
185
    }
186
187
    pub fn accept_terms(&mut self) -> Result<Outcome, Problem> {
188
        if self.floor != Floor::Hotel {
189
            return Err(Problem::new(
190
                StatusCode::NOT_FOUND,
191
                "no-portal",
192
                "There is no captive portal here. There is only the regular kind of captivity.",
193
            ));
194
        }
195
        self.here.wifi_accepted = true;
196
        self.touch();
197
        Ok(Outcome::json(
198
            StatusCode::SEE_OTHER,
199
            json!({ "detail": "Connected. The hotel does not want to know." }),
200
        )
201
        .header(header::LOCATION, self.path("")))
202
    }
203
204
    /// Searching while caged is noisy, and the key only turns up some of the time.
205
    fn rummage_for_key(&mut self) -> Outcome {
206
        let was_below = self.suspicion < SUSPICION_LIMIT;
207
        self.suspicion = (self.suspicion + 1).min(SUSPICION_LIMIT);
208
        self.maybe_summon_mum(was_below);
209
        if !self.chance(KEY_IN_DRAWER) {
210
            return Outcome::empty(StatusCode::NO_CONTENT);
211
        }
212
        self.items.grant(Item::CageKey);
213
        Outcome::json(
214
            StatusCode::OK,
215
            json!({ "detail": "The key was in the drawer. The cage is off." }),
216
        )
217
    }
218
219
    /// Whether searching the drawer on this floor would turn anything up.
220
    pub fn drawer_worth_searching(&self) -> bool {
221
        self.here.drawer_searches == 0 || self.items.cage
222
    }
223
}