atagen

jelq

public

an http based roguelike rpg about jelqing

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

Blame: src/game/checks.rs

8b8e83329a1datagen2026-10-12 12:01 UTC1
use super::{
4f54028cba69atagen2026-10-14 11:45 UTC2
    Lube, Run, Technique,
8b8e83329a1datagen2026-10-12 12:01 UTC3
    balance::STORAGE_BULK,
8b8e83329a1datagen2026-10-12 12:01 UTC4
    lube::LubeRule,
8b8e83329a1datagen2026-10-12 12:01 UTC5
    techniques::{Training, Utility},
8b8e83329a1datagen2026-10-12 12:01 UTC6
};
8b8e83329a1datagen2026-10-12 12:01 UTC7
use crate::problem::Problem;
8b8e83329a1datagen2026-10-12 12:01 UTC8
use axum::http::{StatusCode, header};
8b8e83329a1datagen2026-10-12 12:01 UTC9
use serde_json::json;
8b8e83329a1datagen2026-10-12 12:01 UTC10
4f54028cba69atagen2026-10-14 11:45 UTC11
pub(super) fn caged() -> Problem {
4f54028cba69atagen2026-10-14 11:45 UTC12
    Problem::new(
4f54028cba69atagen2026-10-14 11:45 UTC13
        StatusCode::LOCKED,
4f54028cba69atagen2026-10-14 11:45 UTC14
        "caged",
4f54028cba69atagen2026-10-14 11:45 UTC15
        "The cage is locked. The key is not in the box.",
4f54028cba69atagen2026-10-14 11:45 UTC16
    )
4f54028cba69atagen2026-10-14 11:45 UTC17
}
4f54028cba69atagen2026-10-14 11:45 UTC18
8b8e83329a1datagen2026-10-12 12:01 UTC19
impl Run {
4f54028cba69atagen2026-10-14 11:45 UTC20
    pub(super) fn check_cage(&self) -> Result<(), Problem> {
4f54028cba69atagen2026-10-14 11:45 UTC21
        if self.items.kit.cage {
4f54028cba69atagen2026-10-14 11:45 UTC22
            return Err(caged());
4f54028cba69atagen2026-10-14 11:45 UTC23
        }
4f54028cba69atagen2026-10-14 11:45 UTC24
        Ok(())
4f54028cba69atagen2026-10-14 11:45 UTC25
    }
4f54028cba69atagen2026-10-14 11:45 UTC26
8b8e83329a1datagen2026-10-12 12:01 UTC27
    pub(super) fn ready(&self, technique: Technique, lube: Option<Lube>) -> Result<(), Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC28
        self.access(technique)?;
8b8e83329a1datagen2026-10-12 12:01 UTC29
        match technique {
8b8e83329a1datagen2026-10-12 12:01 UTC30
            Technique::Training(training) => Self::check_lube(training, lube),
8b8e83329a1datagen2026-10-12 12:01 UTC31
            Technique::Utility(_) => Ok(()),
8b8e83329a1datagen2026-10-12 12:01 UTC32
        }
8b8e83329a1datagen2026-10-12 12:01 UTC33
    }
8b8e83329a1datagen2026-10-12 12:01 UTC34
8b8e83329a1datagen2026-10-12 12:01 UTC35
    pub(super) fn access(&self, technique: Technique) -> Result<(), Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC36
        if technique == Technique::Training(Training::Uli) && !self.online.uli_taught {
8b8e83329a1datagen2026-10-12 12:01 UTC37
            return Err(Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC38
                StatusCode::FORBIDDEN,
8b8e83329a1datagen2026-10-12 12:01 UTC39
                "technique-locked",
8b8e83329a1datagen2026-10-12 12:01 UTC40
                "uli is discussed on the forum, in a thread from 2009, by a man who never posted again. You have not found the reply.",
8b8e83329a1datagen2026-10-12 12:01 UTC41
            ));
8b8e83329a1datagen2026-10-12 12:01 UTC42
        }
8b8e83329a1datagen2026-10-12 12:01 UTC43
        if matches!(technique, Technique::Training(_)) {
8b8e83329a1datagen2026-10-12 12:01 UTC44
            self.grip_closes(self.body.grip)?;
8b8e83329a1datagen2026-10-12 12:01 UTC45
        }
8b8e83329a1datagen2026-10-12 12:01 UTC46
        match technique {
8b8e83329a1datagen2026-10-12 12:01 UTC47
            Technique::Utility(Utility::Pump) if !self.items.kit.pump_pro => Err(Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC48
                StatusCode::FAILED_DEPENDENCY,
8b8e83329a1datagen2026-10-12 12:01 UTC49
                "no-pump",
8b8e83329a1datagen2026-10-12 12:01 UTC50
                "The pump technique depends on a pump.",
8b8e83329a1datagen2026-10-12 12:01 UTC51
            )),
8b8e83329a1datagen2026-10-12 12:01 UTC52
            Technique::Utility(_) => Ok(()),
8b8e83329a1datagen2026-10-12 12:01 UTC53
            Technique::Training(training) => match training.profile().foreskin {
8b8e83329a1datagen2026-10-12 12:01 UTC54
                Some((min, max)) => self.check_foreskin(min, max),
8b8e83329a1datagen2026-10-12 12:01 UTC55
                None => Ok(()),
8b8e83329a1datagen2026-10-12 12:01 UTC56
            },
8b8e83329a1datagen2026-10-12 12:01 UTC57
        }
8b8e83329a1datagen2026-10-12 12:01 UTC58
    }
8b8e83329a1datagen2026-10-12 12:01 UTC59
8b8e83329a1datagen2026-10-12 12:01 UTC60
    pub(super) fn check_lube(training: Training, lube: Option<Lube>) -> Result<(), Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC61
        match (training.profile().lube, lube) {
8b8e83329a1datagen2026-10-12 12:01 UTC62
            (LubeRule::Required, None) => Err(Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC63
                StatusCode::UNSUPPORTED_MEDIA_TYPE,
8b8e83329a1datagen2026-10-12 12:01 UTC64
                "lubricant-required",
8b8e83329a1datagen2026-10-12 12:01 UTC65
                format!(
8b8e83329a1datagen2026-10-12 12:01 UTC66
                    "The {} technique requires a lubricant. None was declared.",
8b8e83329a1datagen2026-10-12 12:01 UTC67
                    training.name()
8b8e83329a1datagen2026-10-12 12:01 UTC68
                ),
8b8e83329a1datagen2026-10-12 12:01 UTC69
            )
4f54028cba69atagen2026-10-14 11:45 UTC70
            .with("lubricants", json!(Lube::content_types()))),
8b8e83329a1datagen2026-10-12 12:01 UTC71
            (LubeRule::Forbidden, Some(_)) => Err(Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC72
                StatusCode::UNSUPPORTED_MEDIA_TYPE,
8b8e83329a1datagen2026-10-12 12:01 UTC73
                "lubricant-declared",
8b8e83329a1datagen2026-10-12 12:01 UTC74
                format!(
8b8e83329a1datagen2026-10-12 12:01 UTC75
                    "The {} technique was requested. A lubricant was declared.",
8b8e83329a1datagen2026-10-12 12:01 UTC76
                    training.name()
8b8e83329a1datagen2026-10-12 12:01 UTC77
                ),
8b8e83329a1datagen2026-10-12 12:01 UTC78
            )),
8b8e83329a1datagen2026-10-12 12:01 UTC79
            _ => Ok(()),
8b8e83329a1datagen2026-10-12 12:01 UTC80
        }
8b8e83329a1datagen2026-10-12 12:01 UTC81
    }
8b8e83329a1datagen2026-10-12 12:01 UTC82
8b8e83329a1datagen2026-10-12 12:01 UTC83
    /// One unit of whatever was declared. Spit is free and the tap is always on.
8b8e83329a1datagen2026-10-12 12:01 UTC84
    pub(super) fn lube_available(&self, lube: Option<Lube>) -> Result<(), Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC85
        let Some(bottle) = lube.and_then(Lube::item) else {
8b8e83329a1datagen2026-10-12 12:01 UTC86
            return Ok(());
8b8e83329a1datagen2026-10-12 12:01 UTC87
        };
8b8e83329a1datagen2026-10-12 12:01 UTC88
        if !self.items.has(bottle) {
8b8e83329a1datagen2026-10-12 12:01 UTC89
            return Err(Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC90
                StatusCode::UNSUPPORTED_MEDIA_TYPE,
8b8e83329a1datagen2026-10-12 12:01 UTC91
                "lubricant-exhausted",
8b8e83329a1datagen2026-10-12 12:01 UTC92
                format!("{} was declared, but the bottle is empty.", bottle.slug()),
8b8e83329a1datagen2026-10-12 12:01 UTC93
            ));
8b8e83329a1datagen2026-10-12 12:01 UTC94
        }
8b8e83329a1datagen2026-10-12 12:01 UTC95
        Ok(())
8b8e83329a1datagen2026-10-12 12:01 UTC96
    }
8b8e83329a1datagen2026-10-12 12:01 UTC97
8b8e83329a1datagen2026-10-12 12:01 UTC98
    pub(super) fn take_lube(&mut self, lube: Option<Lube>) -> Result<(), Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC99
        self.lube_available(lube)?;
8b8e83329a1datagen2026-10-12 12:01 UTC100
        if let Some(lube) = lube {
8b8e83329a1datagen2026-10-12 12:01 UTC101
            self.items.lubes.take(lube);
8b8e83329a1datagen2026-10-12 12:01 UTC102
        }
8b8e83329a1datagen2026-10-12 12:01 UTC103
        Ok(())
8b8e83329a1datagen2026-10-12 12:01 UTC104
    }
8b8e83329a1datagen2026-10-12 12:01 UTC105
8b8e83329a1datagen2026-10-12 12:01 UTC106
    pub fn techniques_now(&self) -> impl Iterator<Item = Technique> + '_ {
a11497af08aaatagen2026-10-13 12:04 UTC107
        let blocked = !self.floor.timed() || self.items.kit.cage;
8b8e83329a1datagen2026-10-12 12:01 UTC108
        Technique::ALL
8b8e83329a1datagen2026-10-12 12:01 UTC109
            .into_iter()
8b8e83329a1datagen2026-10-12 12:01 UTC110
            .filter(move |_| !blocked)
a11497af08aaatagen2026-10-13 12:04 UTC111
            .filter(|&technique| {
a11497af08aaatagen2026-10-13 12:04 UTC112
                !self.body.exhausted || technique == Technique::Utility(Utility::Energy)
a11497af08aaatagen2026-10-13 12:04 UTC113
            })
8b8e83329a1datagen2026-10-12 12:01 UTC114
            .filter(|&technique| match technique {
8b8e83329a1datagen2026-10-12 12:01 UTC115
                Technique::Utility(Utility::Warmup) => {
4b1f5f88827datagen2026-10-13 10:18 UTC116
                    !self.here.towel_warmed || self.here.kitchen.kettle_boiled
8b8e83329a1datagen2026-10-12 12:01 UTC117
                }
8b8e83329a1datagen2026-10-12 12:01 UTC118
                Technique::Utility(Utility::Ice) => self.items.stores.ice_packs > 0,
8b8e83329a1datagen2026-10-12 12:01 UTC119
                Technique::Utility(Utility::Supplement) => self.items.stores.supplements > 0,
880b29a0c11eatagen2026-10-13 09:27 UTC120
                Technique::Utility(Utility::Meditate) => {
880b29a0c11eatagen2026-10-13 09:27 UTC121
                    self.online.meditation_taught && !self.here.meditated
880b29a0c11eatagen2026-10-13 09:27 UTC122
                }
8b8e83329a1datagen2026-10-12 12:01 UTC123
                Technique::Utility(Utility::Energy) => self.items.stores.energy_drinks > 0,
8b8e83329a1datagen2026-10-12 12:01 UTC124
                _ => self.access(technique).is_ok() && self.check_storage().is_ok(),
8b8e83329a1datagen2026-10-12 12:01 UTC125
            })
8b8e83329a1datagen2026-10-12 12:01 UTC126
    }
8b8e83329a1datagen2026-10-12 12:01 UTC127
8b8e83329a1datagen2026-10-12 12:01 UTC128
    pub(super) fn check_foreskin(&self, min: f64, max: f64) -> Result<(), Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC129
        let Some(f) = self.body.foreskin else {
8b8e83329a1datagen2026-10-12 12:01 UTC130
            if min <= 0.0 {
8b8e83329a1datagen2026-10-12 12:01 UTC131
                return Ok(());
8b8e83329a1datagen2026-10-12 12:01 UTC132
            }
8b8e83329a1datagen2026-10-12 12:01 UTC133
            return Err(Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC134
                StatusCode::GONE,
8b8e83329a1datagen2026-10-12 12:01 UTC135
                "foreskin-gone",
8b8e83329a1datagen2026-10-12 12:01 UTC136
                "This technique requires a foreskin. The foreskin is gone.",
8b8e83329a1datagen2026-10-12 12:01 UTC137
            )
8b8e83329a1datagen2026-10-12 12:01 UTC138
            .header(header::CONTENT_LOCATION, self.path("/foreskin")));
8b8e83329a1datagen2026-10-12 12:01 UTC139
        };
8b8e83329a1datagen2026-10-12 12:01 UTC140
        if f < min {
8b8e83329a1datagen2026-10-12 12:01 UTC141
            return Err(Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC142
                StatusCode::LENGTH_REQUIRED,
8b8e83329a1datagen2026-10-12 12:01 UTC143
                "foreskin-insufficient",
8b8e83329a1datagen2026-10-12 12:01 UTC144
                format!(
8b8e83329a1datagen2026-10-12 12:01 UTC145
                    "The selected technique requires {min:.1} units of foreskin. {f:.2} units are available. The server has noted the shortfall."
8b8e83329a1datagen2026-10-12 12:01 UTC146
                ),
8b8e83329a1datagen2026-10-12 12:01 UTC147
            )
8b8e83329a1datagen2026-10-12 12:01 UTC148
            .with("required", min));
8b8e83329a1datagen2026-10-12 12:01 UTC149
        }
8b8e83329a1datagen2026-10-12 12:01 UTC150
        if f > max {
8b8e83329a1datagen2026-10-12 12:01 UTC151
            return Err(Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC152
                StatusCode::PAYLOAD_TOO_LARGE,
8b8e83329a1datagen2026-10-12 12:01 UTC153
                "foreskin-excessive",
8b8e83329a1datagen2026-10-12 12:01 UTC154
                format!(
8b8e83329a1datagen2026-10-12 12:01 UTC155
                    "The selected technique tolerates at most {max:.1} units of foreskin. {f:.2} units are present."
8b8e83329a1datagen2026-10-12 12:01 UTC156
                ),
8b8e83329a1datagen2026-10-12 12:01 UTC157
            )
8b8e83329a1datagen2026-10-12 12:01 UTC158
            .with("maximum", max));
8b8e83329a1datagen2026-10-12 12:01 UTC159
        }
8b8e83329a1datagen2026-10-12 12:01 UTC160
        Ok(())
8b8e83329a1datagen2026-10-12 12:01 UTC161
    }
8b8e83329a1datagen2026-10-12 12:01 UTC162
8b8e83329a1datagen2026-10-12 12:01 UTC163
    pub(super) fn check_storage(&self) -> Result<(), Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC164
        if self.items.kit.extender && self.body.girth + self.current_length() / 4.0 > STORAGE_BULK {
8b8e83329a1datagen2026-10-12 12:01 UTC165
            return Err(Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC166
                StatusCode::INSUFFICIENT_STORAGE,
8b8e83329a1datagen2026-10-12 12:01 UTC167
                "jeans-full",
8b8e83329a1datagen2026-10-12 12:01 UTC168
                "The jeans are full. The extender is occupying the remaining storage.",
8b8e83329a1datagen2026-10-12 12:01 UTC169
            )
8b8e83329a1datagen2026-10-12 12:01 UTC170
            .with("extender", self.path("/inventory/extender")));
8b8e83329a1datagen2026-10-12 12:01 UTC171
        }
8b8e83329a1datagen2026-10-12 12:01 UTC172
        Ok(())
8b8e83329a1datagen2026-10-12 12:01 UTC173
    }
8b8e83329a1datagen2026-10-12 12:01 UTC174
}