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