atagen

jelq

public

an http based roguelike rpg about jelqing

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

Blame: src/game/clinic.rs

782069860236atagen2026-10-10 12:44 UTC1
use axum::http::{StatusCode, header};
782069860236atagen2026-10-10 12:44 UTC2
use serde_json::json;
782069860236atagen2026-10-10 12:44 UTC3
8b8e83329a1datagen2026-10-12 12:01 UTC4
use super::{
8b8e83329a1datagen2026-10-12 12:01 UTC5
    Floor, Outcome, Run,
9adb509e6685atagen2026-10-14 08:54 UTC6
    balance::{UNMEASURED_SHAME, UNMEASURED_SHAME_PER_INCH},
8b8e83329a1datagen2026-10-12 12:01 UTC7
    credentials::{Scheme, etag_matches, parse_credentials},
8b8e83329a1datagen2026-10-12 12:01 UTC8
    measure::{round2, volume},
8b8e83329a1datagen2026-10-12 12:01 UTC9
};
058650ec79d6atagen2026-10-12 10:49 UTC10
use crate::{
058650ec79d6atagen2026-10-12 10:49 UTC11
    headers::{SERVER_TIMING, X_DIAGNOSIS},
058650ec79d6atagen2026-10-12 10:49 UTC12
    problem::Problem,
058650ec79d6atagen2026-10-12 10:49 UTC13
};
782069860236atagen2026-10-10 12:44 UTC14
8b8e83329a1datagen2026-10-12 12:01 UTC15
#[derive(Default)]
8b8e83329a1datagen2026-10-12 12:01 UTC16
pub struct Clinic {
8b8e83329a1datagen2026-10-12 12:01 UTC17
    pub(crate) intake_attempts: usize,
8b8e83329a1datagen2026-10-12 12:01 UTC18
    pub(crate) measured: bool,
8b8e83329a1datagen2026-10-12 12:01 UTC19
    pub(crate) prescribed: bool,
8b8e83329a1datagen2026-10-12 12:01 UTC20
    pub(crate) cured: bool,
8b8e83329a1datagen2026-10-12 12:01 UTC21
}
8b8e83329a1datagen2026-10-12 12:01 UTC22
782069860236atagen2026-10-10 12:44 UTC23
const INTAKE: [&str; 3] = [
782069860236atagen2026-10-10 12:44 UTC24
    "The clinic does not accept excuses.",
782069860236atagen2026-10-10 12:44 UTC25
    "The clinic accepts one token. You have not been carrying it.",
782069860236atagen2026-10-10 12:44 UTC26
    "Be honest.",
782069860236atagen2026-10-10 12:44 UTC27
];
782069860236atagen2026-10-10 12:44 UTC28
782069860236atagen2026-10-10 12:44 UTC29
impl Run {
782069860236atagen2026-10-10 12:44 UTC30
    pub fn diagnose(
782069860236atagen2026-10-10 12:44 UTC31
        &mut self,
782069860236atagen2026-10-10 12:44 UTC32
        authorization: Option<&str>,
782069860236atagen2026-10-10 12:44 UTC33
        if_none_match: Option<&str>,
782069860236atagen2026-10-10 12:44 UTC34
    ) -> Result<Outcome, Problem> {
782069860236atagen2026-10-10 12:44 UTC35
        if self.floor != Floor::Clinic {
782069860236atagen2026-10-10 12:44 UTC36
            let floors = Floor::Clinic.number() - self.floor.number();
782069860236atagen2026-10-10 12:44 UTC37
            return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC38
                StatusCode::TOO_EARLY,
782069860236atagen2026-10-10 12:44 UTC39
                "no-appointment",
782069860236atagen2026-10-10 12:44 UTC40
                format!("The clinic's next available appointment is {floors} floors away."),
782069860236atagen2026-10-10 12:44 UTC41
            ));
782069860236atagen2026-10-10 12:44 UTC42
        }
782069860236atagen2026-10-10 12:44 UTC43
        let intake = Problem::new(
782069860236atagen2026-10-10 12:44 UTC44
            StatusCode::UNAUTHORIZED,
782069860236atagen2026-10-10 12:44 UTC45
            "intake",
9683736a2c5eatagen2026-10-10 13:15 UTC46
            "The clinic requires authorisation.",
782069860236atagen2026-10-10 12:44 UTC47
        );
782069860236atagen2026-10-10 12:44 UTC48
        let Some(authorization) = authorization else {
782069860236atagen2026-10-10 12:44 UTC49
            return Err(intake.header(header::WWW_AUTHENTICATE, "Bearer realm=\"clinic\""));
782069860236atagen2026-10-10 12:44 UTC50
        };
8b8e83329a1datagen2026-10-12 12:01 UTC51
        let truthful = parse_credentials(authorization, Scheme::Bearer)
782069860236atagen2026-10-10 12:44 UTC52
            .is_some_and(|token| token.eq_ignore_ascii_case("honesty"));
782069860236atagen2026-10-10 12:44 UTC53
        if !truthful {
782069860236atagen2026-10-10 12:44 UTC54
            let hint = INTAKE[self.clinic.intake_attempts.min(INTAKE.len() - 1)];
782069860236atagen2026-10-10 12:44 UTC55
            self.clinic.intake_attempts += 1;
782069860236atagen2026-10-10 12:44 UTC56
            return Err(intake.header(
782069860236atagen2026-10-10 12:44 UTC57
                header::WWW_AUTHENTICATE,
782069860236atagen2026-10-10 12:44 UTC58
                format!(
782069860236atagen2026-10-10 12:44 UTC59
                    "Bearer realm=\"clinic\", error=\"invalid_token\", error_description=\"{hint}\""
782069860236atagen2026-10-10 12:44 UTC60
                ),
782069860236atagen2026-10-10 12:44 UTC61
            ));
782069860236atagen2026-10-10 12:44 UTC62
        }
782069860236atagen2026-10-10 12:44 UTC63
8b8e83329a1datagen2026-10-12 12:01 UTC64
        self.body.conditions.peyronie = false;
8b8e83329a1datagen2026-10-12 12:01 UTC65
        self.body.conditions.priapism_turns = 0;
782069860236atagen2026-10-10 12:44 UTC66
        let etag = self.origin_etag();
782069860236atagen2026-10-10 12:44 UTC67
        if if_none_match.is_some_and(|inm| etag_matches(inm, &etag)) {
782069860236atagen2026-10-10 12:44 UTC68
            self.clinic.cured = true;
782069860236atagen2026-10-10 12:44 UTC69
            return Ok(Outcome::empty(StatusCode::NOT_MODIFIED)
782069860236atagen2026-10-10 12:44 UTC70
                .header(header::ETAG, etag)
782069860236atagen2026-10-10 12:44 UTC71
                .header(X_DIAGNOSIS, "normal"));
782069860236atagen2026-10-10 12:44 UTC72
        }
782069860236atagen2026-10-10 12:44 UTC73
        if !self.clinic.measured {
782069860236atagen2026-10-10 12:44 UTC74
            self.clinic.measured = true;
8b8e83329a1datagen2026-10-12 12:01 UTC75
            if !self.items.kit.ruler {
9adb509e6685atagen2026-10-14 08:54 UTC76
                self.standing.shame += UNMEASURED_SHAME
9adb509e6685atagen2026-10-14 08:54 UTC77
                    + (self.body.inflation * UNMEASURED_SHAME_PER_INCH).round() as u32;
782069860236atagen2026-10-10 12:44 UTC78
            }
782069860236atagen2026-10-10 12:44 UTC79
        }
8b8e83329a1datagen2026-10-12 12:01 UTC80
        let volume = volume(self.body.length, self.body.girth);
8b8e83329a1datagen2026-10-12 12:01 UTC81
        let foreskin = self.body.foreskin.unwrap_or(0.0);
058650ec79d6atagen2026-10-12 10:49 UTC82
        let timing = format!(
058650ec79d6atagen2026-10-12 10:49 UTC83
            "length;dur={:.2}, girth;dur={:.2}, foreskin;dur={foreskin:.2}, volume;desc=\"cubic inches\";dur={volume:.2}",
8b8e83329a1datagen2026-10-12 12:01 UTC84
            self.body.length, self.body.girth
058650ec79d6atagen2026-10-12 10:49 UTC85
        );
c4d70621d377atagen2026-10-12 11:56 UTC86
        self.clinic.prescribed = true;
c4d70621d377atagen2026-10-12 11:56 UTC87
        self.touch();
782069860236atagen2026-10-10 12:44 UTC88
        Ok(Outcome::json(
782069860236atagen2026-10-10 12:44 UTC89
            StatusCode::OK,
782069860236atagen2026-10-10 12:44 UTC90
            json!({
782069860236atagen2026-10-10 12:44 UTC91
                "diagnosis": "within normal range",
c4d70621d377atagen2026-10-12 11:56 UTC92
                "detail": "Congratulations. You have been cured of a condition you never had. The run is over.",
8b8e83329a1datagen2026-10-12 12:01 UTC93
                "measured_length": round2(self.body.length),
8b8e83329a1datagen2026-10-12 12:01 UTC94
                "measured_girth": round2(self.body.girth),
058650ec79d6atagen2026-10-12 10:49 UTC95
                "volume": round2(volume),
782069860236atagen2026-10-10 12:44 UTC96
                "prescription": "Please stop.",
782069860236atagen2026-10-10 12:44 UTC97
            }),
782069860236atagen2026-10-10 12:44 UTC98
        )
058650ec79d6atagen2026-10-12 10:49 UTC99
        .header(header::ETAG, etag)
058650ec79d6atagen2026-10-12 10:49 UTC100
        .header(SERVER_TIMING, timing))
782069860236atagen2026-10-10 12:44 UTC101
    }
782069860236atagen2026-10-10 12:44 UTC102
}