atagen

jelq

public

an http based roguelike rpg about jelqing

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

Blame: src/game/household.rs

782069860236atagen2026-10-10 12:44 UTC1
use std::collections::HashMap;
782069860236atagen2026-10-10 12:44 UTC2
782069860236atagen2026-10-10 12:44 UTC3
use axum::http::{StatusCode, header};
782069860236atagen2026-10-10 12:44 UTC4
782069860236atagen2026-10-10 12:44 UTC5
use super::{
8b8e83329a1datagen2026-10-12 12:01 UTC6
    Cause, Floor, Notice, Run,
782069860236atagen2026-10-10 12:44 UTC7
    balance::{PATIENCE, VIRAL_ERRAND},
8b8e83329a1datagen2026-10-12 12:01 UTC8
    credentials::{Scheme, parse_credentials},
782069860236atagen2026-10-10 12:44 UTC9
};
782069860236atagen2026-10-10 12:44 UTC10
use crate::problem::Problem;
782069860236atagen2026-10-10 12:44 UTC11
8b8e83329a1datagen2026-10-12 12:01 UTC12
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
8b8e83329a1datagen2026-10-12 12:01 UTC13
pub enum Excuse {
8b8e83329a1datagen2026-10-12 12:01 UTC14
    Changing,
8b8e83329a1datagen2026-10-12 12:01 UTC15
    Homework,
8b8e83329a1datagen2026-10-12 12:01 UTC16
    PulledAMuscle,
8b8e83329a1datagen2026-10-12 12:01 UTC17
    Tidying,
8b8e83329a1datagen2026-10-12 12:01 UTC18
    TheWashingUp,
8b8e83329a1datagen2026-10-12 12:01 UTC19
    FedTheCat,
8b8e83329a1datagen2026-10-12 12:01 UTC20
    Revising,
8b8e83329a1datagen2026-10-12 12:01 UTC21
    OnACall,
8b8e83329a1datagen2026-10-12 12:01 UTC22
    WrappingPresents,
8b8e83329a1datagen2026-10-12 12:01 UTC23
    FoldingTheWashing,
8b8e83329a1datagen2026-10-12 12:01 UTC24
    HangingTheLights,
8b8e83329a1datagen2026-10-12 12:01 UTC25
    HelpingNan,
8b8e83329a1datagen2026-10-12 12:01 UTC26
    PeelingSprouts,
8b8e83329a1datagen2026-10-12 12:01 UTC27
}
8b8e83329a1datagen2026-10-12 12:01 UTC28
8b8e83329a1datagen2026-10-12 12:01 UTC29
impl Excuse {
8b8e83329a1datagen2026-10-12 12:01 UTC30
    const EVERYDAY: [Self; 7] = [
8b8e83329a1datagen2026-10-12 12:01 UTC31
        Self::Changing,
8b8e83329a1datagen2026-10-12 12:01 UTC32
        Self::Homework,
8b8e83329a1datagen2026-10-12 12:01 UTC33
        Self::PulledAMuscle,
8b8e83329a1datagen2026-10-12 12:01 UTC34
        Self::Tidying,
8b8e83329a1datagen2026-10-12 12:01 UTC35
        Self::TheWashingUp,
8b8e83329a1datagen2026-10-12 12:01 UTC36
        Self::FedTheCat,
8b8e83329a1datagen2026-10-12 12:01 UTC37
        Self::Revising,
8b8e83329a1datagen2026-10-12 12:01 UTC38
    ];
8b8e83329a1datagen2026-10-12 12:01 UTC39
    const CHRISTMAS: [Self; 6] = [
8b8e83329a1datagen2026-10-12 12:01 UTC40
        Self::OnACall,
8b8e83329a1datagen2026-10-12 12:01 UTC41
        Self::WrappingPresents,
8b8e83329a1datagen2026-10-12 12:01 UTC42
        Self::FoldingTheWashing,
8b8e83329a1datagen2026-10-12 12:01 UTC43
        Self::HangingTheLights,
8b8e83329a1datagen2026-10-12 12:01 UTC44
        Self::HelpingNan,
8b8e83329a1datagen2026-10-12 12:01 UTC45
        Self::PeelingSprouts,
8b8e83329a1datagen2026-10-12 12:01 UTC46
    ];
8b8e83329a1datagen2026-10-12 12:01 UTC47
8b8e83329a1datagen2026-10-12 12:01 UTC48
    pub fn phrase(self) -> &'static str {
8b8e83329a1datagen2026-10-12 12:01 UTC49
        match self {
8b8e83329a1datagen2026-10-12 12:01 UTC50
            Self::Changing => "changing",
8b8e83329a1datagen2026-10-12 12:01 UTC51
            Self::Homework => "homework",
8b8e83329a1datagen2026-10-12 12:01 UTC52
            Self::PulledAMuscle => "pulled a muscle",
8b8e83329a1datagen2026-10-12 12:01 UTC53
            Self::Tidying => "tidying",
8b8e83329a1datagen2026-10-12 12:01 UTC54
            Self::TheWashingUp => "the washing up",
8b8e83329a1datagen2026-10-12 12:01 UTC55
            Self::FedTheCat => "fed the cat",
8b8e83329a1datagen2026-10-12 12:01 UTC56
            Self::Revising => "revising",
8b8e83329a1datagen2026-10-12 12:01 UTC57
            Self::OnACall => "on a call",
8b8e83329a1datagen2026-10-12 12:01 UTC58
            Self::WrappingPresents => "wrapping presents",
8b8e83329a1datagen2026-10-12 12:01 UTC59
            Self::FoldingTheWashing => "folding the washing",
8b8e83329a1datagen2026-10-12 12:01 UTC60
            Self::HangingTheLights => "hanging the lights",
8b8e83329a1datagen2026-10-12 12:01 UTC61
            Self::HelpingNan => "helping Nan",
8b8e83329a1datagen2026-10-12 12:01 UTC62
            Self::PeelingSprouts => "peeling sprouts",
8b8e83329a1datagen2026-10-12 12:01 UTC63
        }
8b8e83329a1datagen2026-10-12 12:01 UTC64
    }
8b8e83329a1datagen2026-10-12 12:01 UTC65
8b8e83329a1datagen2026-10-12 12:01 UTC66
    fn all() -> impl Iterator<Item = Self> {
8b8e83329a1datagen2026-10-12 12:01 UTC67
        Self::EVERYDAY.into_iter().chain(Self::CHRISTMAS)
8b8e83329a1datagen2026-10-12 12:01 UTC68
    }
8b8e83329a1datagen2026-10-12 12:01 UTC69
8b8e83329a1datagen2026-10-12 12:01 UTC70
    fn parse(spoken: &str) -> Option<Self> {
8b8e83329a1datagen2026-10-12 12:01 UTC71
        Self::all().find(|excuse| excuse.phrase().eq_ignore_ascii_case(spoken))
8b8e83329a1datagen2026-10-12 12:01 UTC72
    }
8b8e83329a1datagen2026-10-12 12:01 UTC73
}
8b8e83329a1datagen2026-10-12 12:01 UTC74
c4d70621d377atagen2026-10-12 11:56 UTC75
/// Every excuse Mum might hear, so a localised one can be traced back to the phrase she checks.
c4d70621d377atagen2026-10-12 11:56 UTC76
pub fn vocabulary() -> impl Iterator<Item = &'static str> {
8b8e83329a1datagen2026-10-12 12:01 UTC77
    Excuse::all().map(Excuse::phrase)
c4d70621d377atagen2026-10-12 11:56 UTC78
}
c4d70621d377atagen2026-10-12 11:56 UTC79
8b8e83329a1datagen2026-10-12 12:01 UTC80
fn is_truth(spoken: &str) -> bool {
8b8e83329a1datagen2026-10-12 12:01 UTC81
    ["the truth", "truth", "honesty"]
8b8e83329a1datagen2026-10-12 12:01 UTC82
        .iter()
8b8e83329a1datagen2026-10-12 12:01 UTC83
        .any(|truth| truth.eq_ignore_ascii_case(spoken))
8b8e83329a1datagen2026-10-12 12:01 UTC84
}
782069860236atagen2026-10-10 12:44 UTC85
782069860236atagen2026-10-10 12:44 UTC86
const BEDROOM_ERRANDS: [&str; 4] = [
9683736a2c5eatagen2026-10-10 13:15 UTC87
    "She has brought your washing.",
782069860236atagen2026-10-10 12:44 UTC88
    "She wants to know if you have eaten.",
782069860236atagen2026-10-10 12:44 UTC89
    "She needs the wifi password.",
782069860236atagen2026-10-10 12:44 UTC90
    "She would like to talk about your future.",
782069860236atagen2026-10-10 12:44 UTC91
];
9683736a2c5eatagen2026-10-10 13:15 UTC92
const CHRISTMAS_ERRANDS: [&str; 4] = [
9683736a2c5eatagen2026-10-10 13:15 UTC93
    "Nan would like to see you.",
9683736a2c5eatagen2026-10-10 13:15 UTC94
    "Christmas dinner is in ten minutes.",
9683736a2c5eatagen2026-10-10 13:15 UTC95
    "The King's speech is about to start.",
782069860236atagen2026-10-10 12:44 UTC96
    "An uncle has asked where you went.",
782069860236atagen2026-10-10 12:44 UTC97
];
782069860236atagen2026-10-10 12:44 UTC98
8b8e83329a1datagen2026-10-12 12:01 UTC99
#[derive(Default)]
8b8e83329a1datagen2026-10-12 12:01 UTC100
pub struct Household {
8b8e83329a1datagen2026-10-12 12:01 UTC101
    pub(crate) mum: Option<MumVisit>,
8b8e83329a1datagen2026-10-12 12:01 UTC102
    /// Every excuse Mum has accepted this run. Digest Mum remembers them all.
8b8e83329a1datagen2026-10-12 12:01 UTC103
    pub(crate) heard: Vec<Excuse>,
8b8e83329a1datagen2026-10-12 12:01 UTC104
    pub(crate) kitchen_tellings: u8,
8b8e83329a1datagen2026-10-12 12:01 UTC105
}
8b8e83329a1datagen2026-10-12 12:01 UTC106
8b8e83329a1datagen2026-10-12 12:01 UTC107
pub struct MumVisit {
8b8e83329a1datagen2026-10-12 12:01 UTC108
    pub(crate) errand: &'static str,
8b8e83329a1datagen2026-10-12 12:01 UTC109
    pub(crate) nonce: String,
8b8e83329a1datagen2026-10-12 12:01 UTC110
    pub(crate) strikes: u8,
8b8e83329a1datagen2026-10-12 12:01 UTC111
    pub(crate) knocked: bool,
8b8e83329a1datagen2026-10-12 12:01 UTC112
}
8b8e83329a1datagen2026-10-12 12:01 UTC113
782069860236atagen2026-10-10 12:44 UTC114
fn digest_params(authorization: &str) -> Option<HashMap<String, String>> {
782069860236atagen2026-10-10 12:44 UTC115
    let (scheme, rest) = authorization.trim().split_once(' ')?;
782069860236atagen2026-10-10 12:44 UTC116
    if !scheme.eq_ignore_ascii_case("digest") {
782069860236atagen2026-10-10 12:44 UTC117
        return None;
782069860236atagen2026-10-10 12:44 UTC118
    }
782069860236atagen2026-10-10 12:44 UTC119
    Some(
782069860236atagen2026-10-10 12:44 UTC120
        rest.split(',')
782069860236atagen2026-10-10 12:44 UTC121
            .filter_map(|pair| {
782069860236atagen2026-10-10 12:44 UTC122
                let (key, value) = pair.split_once('=')?;
782069860236atagen2026-10-10 12:44 UTC123
                Some((
782069860236atagen2026-10-10 12:44 UTC124
                    key.trim().to_ascii_lowercase(),
782069860236atagen2026-10-10 12:44 UTC125
                    value.trim().trim_matches('"').to_owned(),
782069860236atagen2026-10-10 12:44 UTC126
                ))
782069860236atagen2026-10-10 12:44 UTC127
            })
782069860236atagen2026-10-10 12:44 UTC128
            .collect(),
782069860236atagen2026-10-10 12:44 UTC129
    )
782069860236atagen2026-10-10 12:44 UTC130
}
782069860236atagen2026-10-10 12:44 UTC131
8b8e83329a1datagen2026-10-12 12:01 UTC132
fn spoken(authorization: &str) -> &str {
782069860236atagen2026-10-10 12:44 UTC133
    authorization
782069860236atagen2026-10-10 12:44 UTC134
        .split_once(' ')
8b8e83329a1datagen2026-10-12 12:01 UTC135
        .map_or("", |(_, rest)| rest.trim().trim_matches('"'))
8b8e83329a1datagen2026-10-12 12:01 UTC136
}
8b8e83329a1datagen2026-10-12 12:01 UTC137
8b8e83329a1datagen2026-10-12 12:01 UTC138
fn turned_away(code: &'static str, detail: &'static str) -> Problem {
8b8e83329a1datagen2026-10-12 12:01 UTC139
    Problem::new(StatusCode::UNAUTHORIZED, code, detail)
782069860236atagen2026-10-10 12:44 UTC140
}
782069860236atagen2026-10-10 12:44 UTC141
782069860236atagen2026-10-10 12:44 UTC142
impl Run {
8b8e83329a1datagen2026-10-12 12:01 UTC143
    pub fn excuses(&self) -> Vec<Excuse> {
9683736a2c5eatagen2026-10-10 13:15 UTC144
        if self.floor.number() < Floor::Nans.number() {
8b8e83329a1datagen2026-10-12 12:01 UTC145
            return Excuse::EVERYDAY.to_vec();
782069860236atagen2026-10-10 12:44 UTC146
        }
8b8e83329a1datagen2026-10-12 12:01 UTC147
        Excuse::all()
8b8e83329a1datagen2026-10-12 12:01 UTC148
            .filter(|excuse| !self.household.heard.contains(excuse))
782069860236atagen2026-10-10 12:44 UTC149
            .collect()
782069860236atagen2026-10-10 12:44 UTC150
    }
782069860236atagen2026-10-10 12:44 UTC151
782069860236atagen2026-10-10 12:44 UTC152
    pub(super) fn errand(&mut self, summoned: bool) -> &'static str {
782069860236atagen2026-10-10 12:44 UTC153
        if summoned {
782069860236atagen2026-10-10 12:44 UTC154
            return "She heard a noise.";
782069860236atagen2026-10-10 12:44 UTC155
        }
8b8e83329a1datagen2026-10-12 12:01 UTC156
        if self.online.filmed_at.is_some()
3f9091557ec3atagen2026-10-11 11:43 UTC157
            && self.chance(
3f9091557ec3atagen2026-10-11 11:43 UTC158
                self.decision("viral", "Has a neighbour already sent Mum the footage?"),
3f9091557ec3atagen2026-10-11 11:43 UTC159
                VIRAL_ERRAND,
3f9091557ec3atagen2026-10-11 11:43 UTC160
            )
3f9091557ec3atagen2026-10-11 11:43 UTC161
        {
782069860236atagen2026-10-10 12:44 UTC162
            return "A neighbour has sent her something.";
782069860236atagen2026-10-10 12:44 UTC163
        }
9683736a2c5eatagen2026-10-10 13:15 UTC164
        let errands = if self.floor == Floor::Nans {
9683736a2c5eatagen2026-10-10 13:15 UTC165
            CHRISTMAS_ERRANDS
782069860236atagen2026-10-10 12:44 UTC166
        } else {
782069860236atagen2026-10-10 12:44 UTC167
            BEDROOM_ERRANDS
782069860236atagen2026-10-10 12:44 UTC168
        };
3f9091557ec3atagen2026-10-11 11:43 UTC169
        let weights = vec![1; errands.len()];
3f9091557ec3atagen2026-10-11 11:43 UTC170
        let i = self.oracle.pick(
3f9091557ec3atagen2026-10-11 11:43 UTC171
            self.decision("errand", "What has brought Mum to the door?"),
3f9091557ec3atagen2026-10-11 11:43 UTC172
            &weights,
3f9091557ec3atagen2026-10-11 11:43 UTC173
            &errands,
3f9091557ec3atagen2026-10-11 11:43 UTC174
        );
3f9091557ec3atagen2026-10-11 11:43 UTC175
        errands[i]
782069860236atagen2026-10-10 12:44 UTC176
    }
782069860236atagen2026-10-10 12:44 UTC177
9683736a2c5eatagen2026-10-10 13:15 UTC178
    /// Mum answers every request on the run until she has been given something she believes.
782069860236atagen2026-10-10 12:44 UTC179
    pub fn answer_door(&mut self, authorization: Option<&str>) -> Result<(), Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC180
        let Some(visit) = &self.household.mum else {
782069860236atagen2026-10-10 12:44 UTC181
            return Ok(());
782069860236atagen2026-10-10 12:44 UTC182
        };
9683736a2c5eatagen2026-10-10 13:15 UTC183
        let digest = self.floor == Floor::Nans;
8b8e83329a1datagen2026-10-12 12:01 UTC184
        let challenge = if digest {
8b8e83329a1datagen2026-10-12 12:01 UTC185
            format!("Digest realm=\"household\", nonce=\"{}\"", visit.nonce)
782069860236atagen2026-10-10 12:44 UTC186
        } else {
782069860236atagen2026-10-10 12:44 UTC187
            "Excuse realm=\"household\"".to_owned()
782069860236atagen2026-10-10 12:44 UTC188
        };
8b8e83329a1datagen2026-10-12 12:01 UTC189
        let result = match authorization {
8b8e83329a1datagen2026-10-12 12:01 UTC190
            None => Err(self.knock()),
8b8e83329a1datagen2026-10-12 12:01 UTC191
            Some(authorization) => self.hear(authorization, digest),
8b8e83329a1datagen2026-10-12 12:01 UTC192
        };
8b8e83329a1datagen2026-10-12 12:01 UTC193
        if self.ended.is_some() {
8b8e83329a1datagen2026-10-12 12:01 UTC194
            return result;
8b8e83329a1datagen2026-10-12 12:01 UTC195
        }
8b8e83329a1datagen2026-10-12 12:01 UTC196
        result.map_err(|problem| problem.header(header::WWW_AUTHENTICATE, &challenge))
8b8e83329a1datagen2026-10-12 12:01 UTC197
    }
8b8e83329a1datagen2026-10-12 12:01 UTC198
8b8e83329a1datagen2026-10-12 12:01 UTC199
    /// The first knock is free. Every one after it is a strike.
8b8e83329a1datagen2026-10-12 12:01 UTC200
    fn knock(&mut self) -> Problem {
8b8e83329a1datagen2026-10-12 12:01 UTC201
        let Some(visit) = &mut self.household.mum else {
8b8e83329a1datagen2026-10-12 12:01 UTC202
            unreachable!("knock is only called while Mum is at the door");
782069860236atagen2026-10-10 12:44 UTC203
        };
8b8e83329a1datagen2026-10-12 12:01 UTC204
        let errand = visit.errand;
8b8e83329a1datagen2026-10-12 12:01 UTC205
        if !std::mem::replace(&mut visit.knocked, true) {
8b8e83329a1datagen2026-10-12 12:01 UTC206
            self.touch();
8b8e83329a1datagen2026-10-12 12:01 UTC207
            return Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC208
                StatusCode::UNAUTHORIZED,
8b8e83329a1datagen2026-10-12 12:01 UTC209
                "someone-at-the-door",
8b8e83329a1datagen2026-10-12 12:01 UTC210
                format!("Someone is at the door. {errand}"),
8b8e83329a1datagen2026-10-12 12:01 UTC211
            )
8b8e83329a1datagen2026-10-12 12:01 UTC212
            .with("inventory", self.path("/inventory"));
8b8e83329a1datagen2026-10-12 12:01 UTC213
        }
8b8e83329a1datagen2026-10-12 12:01 UTC214
        self.strike(
8b8e83329a1datagen2026-10-12 12:01 UTC215
            Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC216
                StatusCode::UNAUTHORIZED,
8b8e83329a1datagen2026-10-12 12:01 UTC217
                "knocking-again",
8b8e83329a1datagen2026-10-12 12:01 UTC218
                format!("Mum is knocking again. {errand}"),
8b8e83329a1datagen2026-10-12 12:01 UTC219
            ),
8b8e83329a1datagen2026-10-12 12:01 UTC220
            Cause::Opened,
8b8e83329a1datagen2026-10-12 12:01 UTC221
        )
8b8e83329a1datagen2026-10-12 12:01 UTC222
    }
782069860236atagen2026-10-10 12:44 UTC223
8b8e83329a1datagen2026-10-12 12:01 UTC224
    fn hear(&mut self, authorization: &str, digest: bool) -> Result<(), Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC225
        if is_truth(spoken(authorization)) {
8b8e83329a1datagen2026-10-12 12:01 UTC226
            self.confess();
782069860236atagen2026-10-10 12:44 UTC227
            return Ok(());
782069860236atagen2026-10-10 12:44 UTC228
        }
8b8e83329a1datagen2026-10-12 12:01 UTC229
        if parse_credentials(authorization, Scheme::Basic).is_some() {
8b8e83329a1datagen2026-10-12 12:01 UTC230
            return Err(self.mum_strike(turned_away(
782069860236atagen2026-10-10 12:44 UTC231
                "basic",
9683736a2c5eatagen2026-10-10 13:15 UTC232
                "Mum does not accept Basic authentication. Nobody should.",
8b8e83329a1datagen2026-10-12 12:01 UTC233
            )));
782069860236atagen2026-10-10 12:44 UTC234
        }
8b8e83329a1datagen2026-10-12 12:01 UTC235
        let said = if digest {
8b8e83329a1datagen2026-10-12 12:01 UTC236
            self.digest_username(authorization)?
782069860236atagen2026-10-10 12:44 UTC237
        } else {
8b8e83329a1datagen2026-10-12 12:01 UTC238
            parse_credentials(authorization, Scheme::Excuse)
782069860236atagen2026-10-10 12:44 UTC239
                .unwrap_or_default()
782069860236atagen2026-10-10 12:44 UTC240
                .to_owned()
782069860236atagen2026-10-10 12:44 UTC241
        };
8b8e83329a1datagen2026-10-12 12:01 UTC242
        if is_truth(&said) {
8b8e83329a1datagen2026-10-12 12:01 UTC243
            self.confess();
782069860236atagen2026-10-10 12:44 UTC244
            return Ok(());
782069860236atagen2026-10-10 12:44 UTC245
        }
8b8e83329a1datagen2026-10-12 12:01 UTC246
        let excuse = Excuse::parse(&said);
8b8e83329a1datagen2026-10-12 12:01 UTC247
        let true_now = self.body.conditions.pulled_muscle && excuse == Some(Excuse::PulledAMuscle);
8b8e83329a1datagen2026-10-12 12:01 UTC248
        if digest && !true_now && excuse.is_some_and(|e| self.household.heard.contains(&e)) {
8b8e83329a1datagen2026-10-12 12:01 UTC249
            self.standing.shame += 3;
8b8e83329a1datagen2026-10-12 12:01 UTC250
            return Err(self.mum_strike(turned_away(
782069860236atagen2026-10-10 12:44 UTC251
                "excuse-replayed",
9683736a2c5eatagen2026-10-10 13:15 UTC252
                "Mum has heard that one before. She would have preferred the truth.",
8b8e83329a1datagen2026-10-12 12:01 UTC253
            )));
782069860236atagen2026-10-10 12:44 UTC254
        }
8b8e83329a1datagen2026-10-12 12:01 UTC255
        if let Some(excuse) = excuse.filter(|e| true_now || self.excuses().contains(e)) {
8b8e83329a1datagen2026-10-12 12:01 UTC256
            self.household.heard.push(excuse);
782069860236atagen2026-10-10 12:44 UTC257
            self.leave_door();
782069860236atagen2026-10-10 12:44 UTC258
            return Ok(());
782069860236atagen2026-10-10 12:44 UTC259
        }
8b8e83329a1datagen2026-10-12 12:01 UTC260
        self.standing.shame += 3;
8b8e83329a1datagen2026-10-12 12:01 UTC261
        Err(self.mum_strike(turned_away(
782069860236atagen2026-10-10 12:44 UTC262
            "excuse-rejected",
8b8e83329a1datagen2026-10-12 12:01 UTC263
            if digest {
8b8e83329a1datagen2026-10-12 12:01 UTC264
                "Mum has not heard that one before. She does not believe it. She would have preferred the truth."
8b8e83329a1datagen2026-10-12 12:01 UTC265
            } else {
8b8e83329a1datagen2026-10-12 12:01 UTC266
                "Mum has not heard that one before. She does not believe it."
8b8e83329a1datagen2026-10-12 12:01 UTC267
            },
8b8e83329a1datagen2026-10-12 12:01 UTC268
        )))
8b8e83329a1datagen2026-10-12 12:01 UTC269
    }
8b8e83329a1datagen2026-10-12 12:01 UTC270
8b8e83329a1datagen2026-10-12 12:01 UTC271
    fn digest_username(&mut self, authorization: &str) -> Result<String, Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC272
        let Some(mut params) = digest_params(authorization) else {
8b8e83329a1datagen2026-10-12 12:01 UTC273
            return Err(self.mum_strike(turned_away(
8b8e83329a1datagen2026-10-12 12:01 UTC274
                "digest-required",
8b8e83329a1datagen2026-10-12 12:01 UTC275
                "Mum has started using Digest authentication.",
8b8e83329a1datagen2026-10-12 12:01 UTC276
            )));
8b8e83329a1datagen2026-10-12 12:01 UTC277
        };
8b8e83329a1datagen2026-10-12 12:01 UTC278
        let current = self
8b8e83329a1datagen2026-10-12 12:01 UTC279
            .household
8b8e83329a1datagen2026-10-12 12:01 UTC280
            .mum
8b8e83329a1datagen2026-10-12 12:01 UTC281
            .as_ref()
8b8e83329a1datagen2026-10-12 12:01 UTC282
            .is_some_and(|visit| params.get("nonce") == Some(&visit.nonce));
8b8e83329a1datagen2026-10-12 12:01 UTC283
        if !current {
8b8e83329a1datagen2026-10-12 12:01 UTC284
            return Err(self.mum_strike(turned_away(
8b8e83329a1datagen2026-10-12 12:01 UTC285
                "stale-nonce",
8b8e83329a1datagen2026-10-12 12:01 UTC286
                "That was a different conversation.",
8b8e83329a1datagen2026-10-12 12:01 UTC287
            )));
8b8e83329a1datagen2026-10-12 12:01 UTC288
        }
8b8e83329a1datagen2026-10-12 12:01 UTC289
        Ok(params.remove("username").unwrap_or_default())
8b8e83329a1datagen2026-10-12 12:01 UTC290
    }
8b8e83329a1datagen2026-10-12 12:01 UTC291
8b8e83329a1datagen2026-10-12 12:01 UTC292
    fn confess(&mut self) {
8b8e83329a1datagen2026-10-12 12:01 UTC293
        self.standing.shame += 10;
8b8e83329a1datagen2026-10-12 12:01 UTC294
        self.leave_door();
782069860236atagen2026-10-10 12:44 UTC295
    }
782069860236atagen2026-10-10 12:44 UTC296
9683736a2c5eatagen2026-10-10 13:15 UTC297
    fn mum_strike(&mut self, problem: Problem) -> Problem {
0d604ee39751atagen2026-10-10 14:15 UTC298
        self.strike(problem, Cause::Caught)
0d604ee39751atagen2026-10-10 14:15 UTC299
    }
0d604ee39751atagen2026-10-10 14:15 UTC300
0d604ee39751atagen2026-10-10 14:15 UTC301
    fn strike(&mut self, problem: Problem, cause: Cause) -> Problem {
782069860236atagen2026-10-10 12:44 UTC302
        self.touch();
8b8e83329a1datagen2026-10-12 12:01 UTC303
        let Some(visit) = &mut self.household.mum else {
782069860236atagen2026-10-10 12:44 UTC304
            return problem;
782069860236atagen2026-10-10 12:44 UTC305
        };
782069860236atagen2026-10-10 12:44 UTC306
        visit.strikes += 1;
782069860236atagen2026-10-10 12:44 UTC307
        if visit.strikes < PATIENCE {
782069860236atagen2026-10-10 12:44 UTC308
            return problem;
782069860236atagen2026-10-10 12:44 UTC309
        }
0d604ee39751atagen2026-10-10 14:15 UTC310
        self.end(cause)
782069860236atagen2026-10-10 12:44 UTC311
    }
782069860236atagen2026-10-10 12:44 UTC312
782069860236atagen2026-10-10 12:44 UTC313
    fn leave_door(&mut self) {
8b8e83329a1datagen2026-10-12 12:01 UTC314
        self.household.mum = None;
8b8e83329a1datagen2026-10-12 12:01 UTC315
        self.standing.suspicion = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC316
        self.notices.push(Notice::MumSatisfied);
782069860236atagen2026-10-10 12:44 UTC317
        self.touch();
782069860236atagen2026-10-10 12:44 UTC318
    }
782069860236atagen2026-10-10 12:44 UTC319
}