atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
1a144ee92b627d87d2ff575eded19a9b8baa3fe1
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/game/body.rsRust160 lines4.4 KB
1
use axum::http::StatusCode;
2
3
use super::{
4
    Outcome, Run,
5
    balance::{GIRTH_RESPECTED, GIRTH_THICK, LOCKER_ROOM_BAR, MAX_STAMINA, STARTING_EQ},
6
    credentials::etag_matches,
7
};
8
use crate::problem::Problem;
9
10
pub struct Body {
11
    pub(crate) length: f64,
12
    pub(crate) girth: f64,
13
    pub(crate) foreskin: Option<f64>,
14
    pub(crate) inflation: f64,
15
    pub(crate) eq: i32,
16
    pub(crate) stamina: i32,
17
    pub(crate) friction: i32,
18
    pub(crate) grip: Grip,
19
    /// The grip of the last stroke, which decides whether this one gets the switching discount.
20
    pub(crate) jelq_grip: Option<Grip>,
21
    pub(crate) exhausted: bool,
22
    pub(crate) put_applied: bool,
23
    pub(crate) conditions: Conditions,
24
    pub(crate) pending: Vec<Pending>,
25
    pub(crate) arrived: Option<Arrival>,
26
    pub(crate) torn: f64,
27
}
28
29
impl Body {
30
    pub fn new(length: f64, girth: f64, foreskin: f64, inflation: f64) -> Self {
31
        Self {
32
            length,
33
            girth,
34
            foreskin: Some(foreskin),
35
            inflation,
36
            eq: STARTING_EQ,
37
            stamina: MAX_STAMINA,
38
            friction: 0,
39
            grip: Grip::Overhand,
40
            jelq_grip: None,
41
            exhausted: false,
42
            put_applied: false,
43
            conditions: Conditions::default(),
44
            pending: Vec::new(),
45
            arrived: None,
46
            torn: 0.0,
47
        }
48
    }
49
}
50
51
#[derive(Default)]
52
pub struct Conditions {
53
    pub(crate) peyronie: bool,
54
    pub(crate) pulled_muscle: bool,
55
    pub(crate) priapism_turns: u8,
56
    pub(crate) friction_burn: bool,
57
}
58
59
/// Gains a `202` promised, due on a later turn.
60
pub struct Pending {
61
    pub(crate) due: u32,
62
    pub(crate) length: f64,
63
    pub(crate) girth: f64,
64
    pub(crate) foreskin: f64,
65
}
66
67
#[derive(Default)]
68
pub struct Arrival {
69
    pub(crate) length: f64,
70
    pub(crate) girth: f64,
71
    pub(crate) foreskin: Option<f64>,
72
}
73
74
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
75
pub enum Grip {
76
    Overhand,
77
    Underhand,
78
}
79
80
impl Grip {
81
    pub fn name(self) -> &'static str {
82
        match self {
83
            Self::Overhand => "overhand",
84
            Self::Underhand => "underhand",
85
        }
86
    }
87
}
88
89
impl std::str::FromStr for Grip {
90
    type Err = ();
91
92
    fn from_str(protocol: &str) -> Result<Self, ()> {
93
        match protocol.to_ascii_lowercase().as_str() {
94
            "overhand" => Ok(Self::Overhand),
95
            "underhand" => Ok(Self::Underhand),
96
            _ => Err(()),
97
        }
98
    }
99
}
100
101
impl Run {
102
    pub fn delete_foreskin(&mut self, if_match: Option<&str>) -> Result<Outcome, Problem> {
103
        if self.body.foreskin.is_none() {
104
            return Err(Problem::new(
105
                StatusCode::GONE,
106
                "foreskin-gone",
107
                "The foreskin is gone. It will not be returning.",
108
            ));
109
        }
110
        let Some(if_match) = if_match else {
111
            return Err(Problem::new(
112
                StatusCode::PRECONDITION_REQUIRED,
113
                "confirmation-required",
114
                "The server requires you to be sure. Send If-Match.",
115
            ));
116
        };
117
        if !etag_matches(if_match, &self.etag()) {
118
            return Err(Problem::new(
119
                StatusCode::PRECONDITION_FAILED,
120
                "stale-foreskin",
121
                "The foreskin has changed since you last looked at it.",
122
            ));
123
        }
124
        self.body.foreskin = None;
125
        self.standing.shame += 20;
126
        self.touch();
127
        Ok(Outcome::empty(StatusCode::NO_CONTENT))
128
    }
129
130
    pub fn set_grip(&mut self, grip: Grip) -> Result<(), Problem> {
131
        self.grip_closes(grip)?;
132
        self.body.grip = grip;
133
        self.touch();
134
        Ok(())
135
    }
136
137
    pub(super) fn grip_closes(&self, grip: Grip) -> Result<(), Problem> {
138
        if grip == Grip::Underhand && self.body.girth > GIRTH_THICK {
139
            return Err(Problem::new(
140
                StatusCode::PAYLOAD_TOO_LARGE,
141
                "grip-too-small",
142
                format!(
143
                    "The underhand grip no longer closes around {:.2} inches of girth.",
144
                    self.body.girth
145
                ),
146
            )
147
            .with("maximum", GIRTH_THICK));
148
        }
149
        Ok(())
150
    }
151
152
    /// Everyone in the changing room self-reports, so the comparison is self-report against self-report.
153
    pub fn holds_own_in_the_locker_room(&self) -> bool {
154
        self.self_reported() >= LOCKER_ROOM_BAR
155
    }
156
157
    pub fn girth_respected(&self) -> bool {
158
        self.body.girth >= GIRTH_RESPECTED
159
    }
160
}