atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
8b8e83329a1daec70177de3e129964094a711509
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/game/checks.rsRust162 lines6.1 KB
1
use super::{
2
    LUBRICANTS, Lube, Run, Technique,
3
    balance::STORAGE_BULK,
4
    lube::LubeRule,
5
    techniques::{Training, Utility},
6
};
7
use crate::problem::Problem;
8
use axum::http::{StatusCode, header};
9
use serde_json::json;
10
11
impl Run {
12
    pub(super) fn ready(&self, technique: Technique, lube: Option<Lube>) -> Result<(), Problem> {
13
        self.access(technique)?;
14
        match technique {
15
            Technique::Training(training) => Self::check_lube(training, lube),
16
            Technique::Utility(_) => Ok(()),
17
        }
18
    }
19
20
    pub(super) fn access(&self, technique: Technique) -> Result<(), Problem> {
21
        if technique == Technique::Training(Training::Uli) && !self.online.uli_taught {
22
            return Err(Problem::new(
23
                StatusCode::FORBIDDEN,
24
                "technique-locked",
25
                "uli is discussed on the forum, in a thread from 2009, by a man who never posted again. You have not found the reply.",
26
            ));
27
        }
28
        if !self.body.warmed_up {
29
            return Err(Problem::new(
30
                StatusCode::PRECONDITION_FAILED,
31
                "cold-start",
32
                "Warm-up was skipped. The server has noted this.",
33
            )
34
            .with("technique", Utility::Warmup.name()));
35
        }
36
        if matches!(technique, Technique::Training(_)) {
37
            self.grip_closes(self.body.grip)?;
38
        }
39
        match technique {
40
            Technique::Utility(Utility::Pump) if !self.items.kit.pump_pro => Err(Problem::new(
41
                StatusCode::FAILED_DEPENDENCY,
42
                "no-pump",
43
                "The pump technique depends on a pump.",
44
            )),
45
            Technique::Utility(_) => Ok(()),
46
            Technique::Training(training) => match training.profile().foreskin {
47
                Some((min, max)) => self.check_foreskin(min, max),
48
                None => Ok(()),
49
            },
50
        }
51
    }
52
53
    pub(super) fn check_lube(training: Training, lube: Option<Lube>) -> Result<(), Problem> {
54
        match (training.profile().lube, lube) {
55
            (LubeRule::Required, None) => Err(Problem::new(
56
                StatusCode::UNSUPPORTED_MEDIA_TYPE,
57
                "lubricant-required",
58
                format!(
59
                    "The {} technique requires a lubricant. None was declared.",
60
                    training.name()
61
                ),
62
            )
63
            .with("lubricants", json!(LUBRICANTS))),
64
            (LubeRule::Forbidden, Some(_)) => Err(Problem::new(
65
                StatusCode::UNSUPPORTED_MEDIA_TYPE,
66
                "lubricant-declared",
67
                format!(
68
                    "The {} technique was requested. A lubricant was declared.",
69
                    training.name()
70
                ),
71
            )),
72
            _ => Ok(()),
73
        }
74
    }
75
76
    /// One unit of whatever was declared. Spit is free and the tap is always on.
77
    pub(super) fn lube_available(&self, lube: Option<Lube>) -> Result<(), Problem> {
78
        let Some(bottle) = lube.and_then(Lube::item) else {
79
            return Ok(());
80
        };
81
        if !self.items.has(bottle) {
82
            return Err(Problem::new(
83
                StatusCode::UNSUPPORTED_MEDIA_TYPE,
84
                "lubricant-exhausted",
85
                format!("{} was declared, but the bottle is empty.", bottle.slug()),
86
            ));
87
        }
88
        Ok(())
89
    }
90
91
    pub(super) fn take_lube(&mut self, lube: Option<Lube>) -> Result<(), Problem> {
92
        self.lube_available(lube)?;
93
        if let Some(lube) = lube {
94
            self.items.lubes.take(lube);
95
        }
96
        Ok(())
97
    }
98
99
    pub fn techniques_now(&self) -> impl Iterator<Item = Technique> + '_ {
100
        let blocked = !self.floor.timed() || self.items.kit.cage || self.body.exhausted;
101
        Technique::ALL
102
            .into_iter()
103
            .filter(move |_| !blocked)
104
            .filter(|&technique| match technique {
105
                Technique::Utility(Utility::Warmup) => {
106
                    !self.body.warmed_up || self.here.kitchen.kettle_boiled
107
                }
108
                Technique::Utility(Utility::Ice) => self.items.stores.ice_packs > 0,
109
                Technique::Utility(Utility::Supplement) => self.items.stores.supplements > 0,
110
                Technique::Utility(Utility::Meditate) => !self.here.meditated,
111
                Technique::Utility(Utility::Energy) => self.items.stores.energy_drinks > 0,
112
                _ => self.access(technique).is_ok() && self.check_storage().is_ok(),
113
            })
114
    }
115
116
    pub(super) fn check_foreskin(&self, min: f64, max: f64) -> Result<(), Problem> {
117
        let Some(f) = self.body.foreskin else {
118
            if min <= 0.0 {
119
                return Ok(());
120
            }
121
            return Err(Problem::new(
122
                StatusCode::GONE,
123
                "foreskin-gone",
124
                "This technique requires a foreskin. The foreskin is gone.",
125
            )
126
            .header(header::CONTENT_LOCATION, self.path("/foreskin")));
127
        };
128
        if f < min {
129
            return Err(Problem::new(
130
                StatusCode::LENGTH_REQUIRED,
131
                "foreskin-insufficient",
132
                format!(
133
                    "The selected technique requires {min:.1} units of foreskin. {f:.2} units are available. The server has noted the shortfall."
134
                ),
135
            )
136
            .with("required", min));
137
        }
138
        if f > max {
139
            return Err(Problem::new(
140
                StatusCode::PAYLOAD_TOO_LARGE,
141
                "foreskin-excessive",
142
                format!(
143
                    "The selected technique tolerates at most {max:.1} units of foreskin. {f:.2} units are present."
144
                ),
145
            )
146
            .with("maximum", max));
147
        }
148
        Ok(())
149
    }
150
151
    pub(super) fn check_storage(&self) -> Result<(), Problem> {
152
        if self.items.kit.extender && self.body.girth + self.current_length() / 4.0 > STORAGE_BULK {
153
            return Err(Problem::new(
154
                StatusCode::INSUFFICIENT_STORAGE,
155
                "jeans-full",
156
                "The jeans are full. The extender is occupying the remaining storage.",
157
            )
158
            .with("extender", self.path("/inventory/extender")));
159
        }
160
        Ok(())
161
    }
162
}