atagen

jelq

public

an http based roguelike rpg about jelqing

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