atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
3f9091557ec3a198324147b05397e1bf5c50247a
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/game/techniques.rsRust241 lines6.7 KB
1
use axum::http::StatusCode;
2
use serde_json::{Value, json};
3
4
use crate::problem::Problem;
5
6
pub const LUBRICANTS: [&str; 5] = [
7
    "application/baby-oil",
8
    "application/coconut-oil",
9
    "application/lotion",
10
    "application/vaseline",
11
    "text/plain; charset=spit",
12
];
13
14
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
15
pub enum Lube {
16
    BabyOil,
17
    CoconutOil,
18
    Lotion,
19
    Vaseline,
20
    Spit,
21
}
22
23
impl Lube {
24
    pub fn from_content_type(content_type: Option<&str>) -> Result<Option<Self>, Problem> {
25
        let Some(raw) = content_type else {
26
            return Ok(None);
27
        };
28
        let lowered = raw.to_ascii_lowercase();
29
        let mut parts = lowered.split(';').map(str::trim);
30
        let essence = parts.next().unwrap_or_default();
31
        let spit = parts.any(|p| p.replace(' ', "") == "charset=spit");
32
        match essence {
33
            "application/baby-oil" => Ok(Some(Self::BabyOil)),
34
            "application/coconut-oil" => Ok(Some(Self::CoconutOil)),
35
            "application/lotion" => Ok(Some(Self::Lotion)),
36
            "application/vaseline" => Ok(Some(Self::Vaseline)),
37
            "text/plain" if spit => Ok(Some(Self::Spit)),
38
            e if e.starts_with("image/") || e.starts_with("video/") => Err(Problem::new(
39
                StatusCode::UNSUPPORTED_MEDIA_TYPE,
40
                "no",
41
                "No.",
42
            )),
43
            _ => Err(Problem::new(
44
                StatusCode::UNSUPPORTED_MEDIA_TYPE,
45
                "unsupported-lubricant",
46
                format!("{essence} is not a lubricant."),
47
            )
48
            .with("lubricants", json!(LUBRICANTS))),
49
        }
50
    }
51
52
    pub fn modifier(self) -> i32 {
53
        match self {
54
            Self::BabyOil => 1,
55
            Self::CoconutOil => 3,
56
            Self::Lotion => 0,
57
            Self::Vaseline => 2,
58
            Self::Spit => -2,
59
        }
60
    }
61
}
62
63
/// Techniques that roll for gains.
64
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
65
pub enum Training {
66
    Wet,
67
    Dry,
68
    Stretch,
69
    Uli,
70
}
71
72
/// Techniques that change EQ or spend an item without rolling.
73
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
74
pub enum Utility {
75
    Warmup,
76
    Ice,
77
    Edge,
78
    Pump,
79
    Supplement,
80
    Meditate,
81
    Energy,
82
}
83
84
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
85
pub enum Technique {
86
    Training(Training),
87
    Utility(Utility),
88
}
89
90
#[derive(Clone, Copy)]
91
pub enum LubeRule {
92
    Required,
93
    Forbidden,
94
    Optional,
95
}
96
97
impl LubeRule {
98
    fn name(self) -> &'static str {
99
        match self {
100
            Self::Required => "required",
101
            Self::Forbidden => "forbidden",
102
            Self::Optional => "optional",
103
        }
104
    }
105
}
106
107
pub struct Profile {
108
    pub dc: i32,
109
    pub length: f64,
110
    pub girth: f64,
111
    /// Minimum and maximum foreskin the technique tolerates. A minimum above zero means it is used.
112
    pub foreskin: Option<(f64, f64)>,
113
    pub foreskin_growth: f64,
114
    pub lube: LubeRule,
115
}
116
117
impl Training {
118
    pub fn name(self) -> &'static str {
119
        match self {
120
            Self::Wet => "wet",
121
            Self::Dry => "dry",
122
            Self::Stretch => "stretch",
123
            Self::Uli => "uli",
124
        }
125
    }
126
127
    pub fn profile(self) -> Profile {
128
        match self {
129
            Self::Wet => Profile {
130
                dc: 10,
131
                length: 0.010,
132
                girth: 0.005,
133
                foreskin: None,
134
                foreskin_growth: 0.0,
135
                lube: LubeRule::Required,
136
            },
137
            Self::Dry => Profile {
138
                dc: 12,
139
                length: 0.008,
140
                girth: 0.003,
141
                foreskin: Some((0.8, 2.0)),
142
                foreskin_growth: 0.01,
143
                lube: LubeRule::Forbidden,
144
            },
145
            Self::Stretch => Profile {
146
                dc: 11,
147
                length: 0.015,
148
                girth: 0.0,
149
                foreskin: Some((0.0, 1.8)),
150
                foreskin_growth: 0.03,
151
                lube: LubeRule::Optional,
152
            },
153
            Self::Uli => Profile {
154
                dc: 15,
155
                length: 0.002,
156
                girth: 0.020,
157
                foreskin: None,
158
                foreskin_growth: 0.0,
159
                lube: LubeRule::Optional,
160
            },
161
        }
162
    }
163
}
164
165
impl Utility {
166
    pub fn name(self) -> &'static str {
167
        match self {
168
            Self::Warmup => "warmup",
169
            Self::Ice => "ice",
170
            Self::Edge => "edge",
171
            Self::Pump => "pump",
172
            Self::Supplement => "supplement",
173
            Self::Meditate => "meditate",
174
            Self::Energy => "energy",
175
        }
176
    }
177
178
    fn effect(self) -> &'static str {
179
        match self {
180
            Self::Warmup => "EQ +25. Satisfies the warm-up precondition.",
181
            Self::Ice => "EQ -40. Treats a friction burn. Consumes an ice pack.",
182
            Self::Edge => "EQ +15 and a little girth. Three in a row is a loop.",
183
            Self::Pump => "Large gains, all temporary. Requires a pump.",
184
            Self::Supplement => "Consumes a supplement.",
185
            Self::Meditate => "EQ -10, sheds 2 friction. Taught by the forum. Once per floor.",
186
            Self::Energy => "+3 stamina. Consumes an energy drink. Tastes like a battery.",
187
        }
188
    }
189
}
190
191
impl Technique {
192
    pub const ALL: [Self; 11] = [
193
        Self::Utility(Utility::Warmup),
194
        Self::Utility(Utility::Ice),
195
        Self::Utility(Utility::Edge),
196
        Self::Utility(Utility::Pump),
197
        Self::Utility(Utility::Supplement),
198
        Self::Utility(Utility::Meditate),
199
        Self::Utility(Utility::Energy),
200
        Self::Training(Training::Wet),
201
        Self::Training(Training::Dry),
202
        Self::Training(Training::Stretch),
203
        Self::Training(Training::Uli),
204
    ];
205
206
    pub fn name(self) -> &'static str {
207
        match self {
208
            Self::Training(t) => t.name(),
209
            Self::Utility(u) => u.name(),
210
        }
211
    }
212
213
    pub fn parse(name: &str) -> Option<Self> {
214
        Self::ALL.into_iter().find(|t| t.name() == name)
215
    }
216
217
    pub fn lubricant(self) -> Option<&'static str> {
218
        match self {
219
            Self::Training(training) => Some(training.profile().lube.name()),
220
            Self::Utility(_) => None,
221
        }
222
    }
223
224
    pub fn describe(self, uli_taught: bool) -> Value {
225
        let training = match self {
226
            Self::Utility(utility) => {
227
                return json!({ "name": utility.name(), "effect": utility.effect() });
228
            }
229
            Self::Training(training) => training,
230
        };
231
        let profile = training.profile();
232
        let lubricant = profile.lube.name();
233
        json!({
234
            "name": training.name(),
235
            "dc": profile.dc,
236
            "lubricant": lubricant,
237
            "foreskin": profile.foreskin.map(|(min, max)| json!({ "min": min, "max": max })),
238
            "locked": training == Training::Uli && !uli_taught,
239
        })
240
    }
241
}