atagen

jelq

public

an http based roguelike rpg about jelqing

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