atagen

jelq

public

an http based roguelike rpg about jelqing

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