atagen

jelq

public

an http based roguelike rpg about jelqing

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