atagen

jelq

public

an http based roguelike rpg about jelqing

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