atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
7288fc269bb68466e68614d7f97cb55fc4ae1358
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,
9683736a2c5eatagen2026-10-10 13:15 UTC6
    balance::{FLOOR_ALLOWANCE, FLOOR_TURNS, STARTING_EQ, ZIP},
782069860236atagen2026-10-10 12:44 UTC7
    round2,
782069860236atagen2026-10-10 12:44 UTC8
};
782069860236atagen2026-10-10 12:44 UTC9
use crate::problem::Problem;
782069860236atagen2026-10-10 12:44 UTC10
782069860236atagen2026-10-10 12:44 UTC11
impl Run {
782069860236atagen2026-10-10 12:44 UTC12
    pub(super) fn next_floor(&mut self, paid: bool) {
782069860236atagen2026-10-10 12:44 UTC13
        let Some(next) = self.floor.next() else {
782069860236atagen2026-10-10 12:44 UTC14
            return;
782069860236atagen2026-10-10 12:44 UTC15
        };
782069860236atagen2026-10-10 12:44 UTC16
        self.floor = next;
782069860236atagen2026-10-10 12:44 UTC17
        self.here = FloorState::default();
782069860236atagen2026-10-10 12:44 UTC18
        self.warmed_up = false;
782069860236atagen2026-10-10 12:44 UTC19
        self.eq = STARTING_EQ;
782069860236atagen2026-10-10 12:44 UTC20
        self.suspicion = 0;
9683736a2c5eatagen2026-10-10 13:15 UTC21
        self.mum = None;
782069860236atagen2026-10-10 12:44 UTC22
        if paid {
782069860236atagen2026-10-10 12:44 UTC23
            self.cash += FLOOR_ALLOWANCE;
782069860236atagen2026-10-10 12:44 UTC24
        }
782069860236atagen2026-10-10 12:44 UTC25
        self.touch();
782069860236atagen2026-10-10 12:44 UTC26
    }
782069860236atagen2026-10-10 12:44 UTC27
782069860236atagen2026-10-10 12:44 UTC28
    pub fn advance(&mut self) -> Result<Outcome, Problem> {
782069860236atagen2026-10-10 12:44 UTC29
        match self.floor {
782069860236atagen2026-10-10 12:44 UTC30
            Floor::Clinic => {
782069860236atagen2026-10-10 12:44 UTC31
                return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC32
                    StatusCode::CONFLICT,
782069860236atagen2026-10-10 12:44 UTC33
                    "no-further-floor",
782069860236atagen2026-10-10 12:44 UTC34
                    "There is no further floor. There is only the diagnosis.",
782069860236atagen2026-10-10 12:44 UTC35
                ));
782069860236atagen2026-10-10 12:44 UTC36
            }
782069860236atagen2026-10-10 12:44 UTC37
            Floor::Forum if self.elder != Elder::Beaten => {
782069860236atagen2026-10-10 12:44 UTC38
                return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC39
                    StatusCode::CONFLICT,
782069860236atagen2026-10-10 12:44 UTC40
                    "elder-unaddressed",
782069860236atagen2026-10-10 12:44 UTC41
                    "The Elder has not been addressed.",
782069860236atagen2026-10-10 12:44 UTC42
                )
782069860236atagen2026-10-10 12:44 UTC43
                .with("elder", self.path("/elder")));
782069860236atagen2026-10-10 12:44 UTC44
            }
782069860236atagen2026-10-10 12:44 UTC45
            floor if floor.timed() && self.here.turns < FLOOR_TURNS => {
782069860236atagen2026-10-10 12:44 UTC46
                return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC47
                    StatusCode::TOO_EARLY,
782069860236atagen2026-10-10 12:44 UTC48
                    "floor-not-over",
782069860236atagen2026-10-10 12:44 UTC49
                    format!(
782069860236atagen2026-10-10 12:44 UTC50
                        "The floor is not over. {} turns remain.",
782069860236atagen2026-10-10 12:44 UTC51
                        FLOOR_TURNS - self.here.turns
782069860236atagen2026-10-10 12:44 UTC52
                    ),
782069860236atagen2026-10-10 12:44 UTC53
                ));
782069860236atagen2026-10-10 12:44 UTC54
            }
782069860236atagen2026-10-10 12:44 UTC55
            _ => {}
782069860236atagen2026-10-10 12:44 UTC56
        }
9683736a2c5eatagen2026-10-10 13:15 UTC57
        if self.chance(ZIP) {
9683736a2c5eatagen2026-10-10 13:15 UTC58
            return Err(self.end(Cause::Zip));
782069860236atagen2026-10-10 12:44 UTC59
        }
782069860236atagen2026-10-10 12:44 UTC60
        let reverted = self.here.temp_length;
782069860236atagen2026-10-10 12:44 UTC61
        self.next_floor(true);
782069860236atagen2026-10-10 12:44 UTC62
        let mut body = json!({
782069860236atagen2026-10-10 12:44 UTC63
            "detail": format!("You have arrived at {}.", self.floor.name()),
782069860236atagen2026-10-10 12:44 UTC64
            "note": self.floor.note(),
782069860236atagen2026-10-10 12:44 UTC65
        });
782069860236atagen2026-10-10 12:44 UTC66
        if reverted > 0.0 {
782069860236atagen2026-10-10 12:44 UTC67
            body["reverted"] = json!(round2(reverted));
782069860236atagen2026-10-10 12:44 UTC68
        }
782069860236atagen2026-10-10 12:44 UTC69
        Ok(
782069860236atagen2026-10-10 12:44 UTC70
            Outcome::json(StatusCode::SEE_OTHER, body)
782069860236atagen2026-10-10 12:44 UTC71
                .header(header::LOCATION, self.path("/floor")),
782069860236atagen2026-10-10 12:44 UTC72
        )
782069860236atagen2026-10-10 12:44 UTC73
    }
782069860236atagen2026-10-10 12:44 UTC74
782069860236atagen2026-10-10 12:44 UTC75
    pub fn floor_view(&self) -> Value {
782069860236atagen2026-10-10 12:44 UTC76
        let mut view = json!({
782069860236atagen2026-10-10 12:44 UTC77
            "name": self.floor.name(),
782069860236atagen2026-10-10 12:44 UTC78
            "note": self.floor.note(),
782069860236atagen2026-10-10 12:44 UTC79
            "turns": self.here.turns,
782069860236atagen2026-10-10 12:44 UTC80
            "turns_required": self.floor.timed().then_some(FLOOR_TURNS),
782069860236atagen2026-10-10 12:44 UTC81
            "exit": { "href": self.path("/floor"), "method": "POST" },
782069860236atagen2026-10-10 12:44 UTC82
        });
782069860236atagen2026-10-10 12:44 UTC83
        match self.floor {
9683736a2c5eatagen2026-10-10 13:15 UTC84
            Floor::ChangingRoom => {
782069860236atagen2026-10-10 12:44 UTC85
                view["others"] = json!([
782069860236atagen2026-10-10 12:44 UTC86
                    { "name": "Greg", "self_reported_length": 9.5 },
782069860236atagen2026-10-10 12:44 UTC87
                    { "name": "Dmitri", "self_reported_length": 8.75 },
782069860236atagen2026-10-10 12:44 UTC88
                    { "name": "a man in a towel", "self_reported_length": 11.0 },
782069860236atagen2026-10-10 12:44 UTC89
                ]);
782069860236atagen2026-10-10 12:44 UTC90
            }
782069860236atagen2026-10-10 12:44 UTC91
            Floor::Hotel => view["portal"] = json!(self.path("/portal")),
782069860236atagen2026-10-10 12:44 UTC92
            Floor::Forum => {
782069860236atagen2026-10-10 12:44 UTC93
                view["thread"] = json!(super::FORUM_THREAD);
782069860236atagen2026-10-10 12:44 UTC94
                view["elder"] = json!(self.path("/elder"));
782069860236atagen2026-10-10 12:44 UTC95
            }
782069860236atagen2026-10-10 12:44 UTC96
            Floor::Clinic => view["diagnosis"] = json!(self.path("/diagnosis")),
782069860236atagen2026-10-10 12:44 UTC97
            _ => {}
782069860236atagen2026-10-10 12:44 UTC98
        }
782069860236atagen2026-10-10 12:44 UTC99
        if self.floor.shop_open() {
782069860236atagen2026-10-10 12:44 UTC100
            view["shop"] = json!(self.path("/shop"));
782069860236atagen2026-10-10 12:44 UTC101
        }
782069860236atagen2026-10-10 12:44 UTC102
        view
782069860236atagen2026-10-10 12:44 UTC103
    }
782069860236atagen2026-10-10 12:44 UTC104
782069860236atagen2026-10-10 12:44 UTC105
    fn found(&self, item: Item, detail: &str) -> Outcome {
782069860236atagen2026-10-10 12:44 UTC106
        Outcome::json(StatusCode::FOUND, json!({ "detail": detail })).header(
782069860236atagen2026-10-10 12:44 UTC107
            header::LOCATION,
782069860236atagen2026-10-10 12:44 UTC108
            self.path(&format!("/inventory/{}", item.slug())),
782069860236atagen2026-10-10 12:44 UTC109
        )
782069860236atagen2026-10-10 12:44 UTC110
    }
782069860236atagen2026-10-10 12:44 UTC111
782069860236atagen2026-10-10 12:44 UTC112
    pub fn search_drawer(&mut self) -> Result<Outcome, Problem> {
782069860236atagen2026-10-10 12:44 UTC113
        self.here.drawer_searches += 1;
782069860236atagen2026-10-10 12:44 UTC114
        self.touch();
782069860236atagen2026-10-10 12:44 UTC115
        let first = self.here.drawer_searches == 1;
782069860236atagen2026-10-10 12:44 UTC116
        match self.floor {
782069860236atagen2026-10-10 12:44 UTC117
            Floor::Bedroom if first && !self.items.ruler => {
782069860236atagen2026-10-10 12:44 UTC118
                self.items.grant(Item::Ruler);
782069860236atagen2026-10-10 12:44 UTC119
                self.shame += 10;
782069860236atagen2026-10-10 12:44 UTC120
                Ok(self.found(
782069860236atagen2026-10-10 12:44 UTC121
                    Item::Ruler,
782069860236atagen2026-10-10 12:44 UTC122
                    "You now know. This is the first honest measurement of the run.",
782069860236atagen2026-10-10 12:44 UTC123
                ))
782069860236atagen2026-10-10 12:44 UTC124
            }
782069860236atagen2026-10-10 12:44 UTC125
            Floor::Bathroom if first => {
782069860236atagen2026-10-10 12:44 UTC126
                self.items.grant(Item::DoorLock);
782069860236atagen2026-10-10 12:44 UTC127
                Ok(self.found(Item::DoorLock, "A door lock. It works once."))
782069860236atagen2026-10-10 12:44 UTC128
            }
9683736a2c5eatagen2026-10-10 13:15 UTC129
            Floor::ChangingRoom => {
782069860236atagen2026-10-10 12:44 UTC130
                self.shame += 3;
782069860236atagen2026-10-10 12:44 UTC131
                Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC132
                    StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS,
782069860236atagen2026-10-10 12:44 UTC133
                    "not-your-locker",
782069860236atagen2026-10-10 12:44 UTC134
                    "That locker belongs to Greg. The server cannot serve this request for legal reasons.",
782069860236atagen2026-10-10 12:44 UTC135
                ))
782069860236atagen2026-10-10 12:44 UTC136
            }
9683736a2c5eatagen2026-10-10 13:15 UTC137
            Floor::Nans if first => {
782069860236atagen2026-10-10 12:44 UTC138
                self.items.grant(Item::Cage);
782069860236atagen2026-10-10 12:44 UTC139
                self.shame += 5;
782069860236atagen2026-10-10 12:44 UTC140
                Ok(self.found(
782069860236atagen2026-10-10 12:44 UTC141
                    Item::Cage,
782069860236atagen2026-10-10 12:44 UTC142
                    "A small box. Inside was a cage. It is now on. The key is not in the box.",
782069860236atagen2026-10-10 12:44 UTC143
                ))
782069860236atagen2026-10-10 12:44 UTC144
            }
9683736a2c5eatagen2026-10-10 13:15 UTC145
            Floor::Nans if self.items.cage => {
782069860236atagen2026-10-10 12:44 UTC146
                self.items.grant(Item::CageKey);
782069860236atagen2026-10-10 12:44 UTC147
                Ok(Outcome::json(
782069860236atagen2026-10-10 12:44 UTC148
                    StatusCode::OK,
782069860236atagen2026-10-10 12:44 UTC149
                    json!({ "detail": "The key was under where the box had been. The cage is off." }),
782069860236atagen2026-10-10 12:44 UTC150
                ))
782069860236atagen2026-10-10 12:44 UTC151
            }
782069860236atagen2026-10-10 12:44 UTC152
            Floor::Hotel if first => {
782069860236atagen2026-10-10 12:44 UTC153
                self.items.grant(Item::DndSign);
782069860236atagen2026-10-10 12:44 UTC154
                self.items.grant(Item::IcePack);
782069860236atagen2026-10-10 12:44 UTC155
                self.items.grant(Item::IcePack);
782069860236atagen2026-10-10 12:44 UTC156
                Ok(self.found(
782069860236atagen2026-10-10 12:44 UTC157
                    Item::DndSign,
782069860236atagen2026-10-10 12:44 UTC158
                    "A Do Not Disturb sign, and two ice packs from the machine down the hall.",
782069860236atagen2026-10-10 12:44 UTC159
                ))
782069860236atagen2026-10-10 12:44 UTC160
            }
782069860236atagen2026-10-10 12:44 UTC161
            Floor::Forum if first => {
782069860236atagen2026-10-10 12:44 UTC162
                self.items.grant(Item::Spreadsheet);
782069860236atagen2026-10-10 12:44 UTC163
                Ok(self.found(
782069860236atagen2026-10-10 12:44 UTC164
                    Item::Spreadsheet,
782069860236atagen2026-10-10 12:44 UTC165
                    "Your own spreadsheet. Every line is flat.",
782069860236atagen2026-10-10 12:44 UTC166
                ))
782069860236atagen2026-10-10 12:44 UTC167
            }
782069860236atagen2026-10-10 12:44 UTC168
            Floor::Clinic => Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC169
                StatusCode::FORBIDDEN,
782069860236atagen2026-10-10 12:44 UTC170
                "doctors-drawer",
782069860236atagen2026-10-10 12:44 UTC171
                "That is the doctor's drawer.",
782069860236atagen2026-10-10 12:44 UTC172
            )),
782069860236atagen2026-10-10 12:44 UTC173
            _ => Ok(Outcome::empty(StatusCode::NO_CONTENT)),
782069860236atagen2026-10-10 12:44 UTC174
        }
782069860236atagen2026-10-10 12:44 UTC175
    }
782069860236atagen2026-10-10 12:44 UTC176
782069860236atagen2026-10-10 12:44 UTC177
    pub fn accept_terms(&mut self) -> Result<Outcome, Problem> {
782069860236atagen2026-10-10 12:44 UTC178
        if self.floor != Floor::Hotel {
782069860236atagen2026-10-10 12:44 UTC179
            return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC180
                StatusCode::NOT_FOUND,
782069860236atagen2026-10-10 12:44 UTC181
                "no-portal",
782069860236atagen2026-10-10 12:44 UTC182
                "There is no captive portal here. There is only the regular kind of captivity.",
782069860236atagen2026-10-10 12:44 UTC183
            ));
782069860236atagen2026-10-10 12:44 UTC184
        }
782069860236atagen2026-10-10 12:44 UTC185
        self.here.wifi_accepted = true;
782069860236atagen2026-10-10 12:44 UTC186
        self.touch();
782069860236atagen2026-10-10 12:44 UTC187
        Ok(Outcome::json(
782069860236atagen2026-10-10 12:44 UTC188
            StatusCode::SEE_OTHER,
782069860236atagen2026-10-10 12:44 UTC189
            json!({ "detail": "Connected. The hotel does not want to know." }),
782069860236atagen2026-10-10 12:44 UTC190
        )
782069860236atagen2026-10-10 12:44 UTC191
        .header(header::LOCATION, self.path("")))
782069860236atagen2026-10-10 12:44 UTC192
    }
782069860236atagen2026-10-10 12:44 UTC193
}