atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
880b29a0c11ef3e07c651d4d4b81c357e971f568
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/game/body.rsRust149 lines4.1 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) warmed_up: bool,
21
    pub(crate) exhausted: bool,
22
    pub(crate) put_applied: bool,
23
    pub(crate) conditions: Conditions,
24
    pub(crate) pending: Vec<Pending>,
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
            warmed_up: false,
39
            exhausted: false,
40
            put_applied: false,
41
            conditions: Conditions::default(),
42
            pending: Vec::new(),
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(Clone, Copy, PartialEq, Eq, Debug)]
64
pub enum Grip {
65
    Overhand,
66
    Underhand,
67
}
68
69
impl Grip {
70
    pub fn name(self) -> &'static str {
71
        match self {
72
            Self::Overhand => "overhand",
73
            Self::Underhand => "underhand",
74
        }
75
    }
76
}
77
78
impl std::str::FromStr for Grip {
79
    type Err = ();
80
81
    fn from_str(protocol: &str) -> Result<Self, ()> {
82
        match protocol.to_ascii_lowercase().as_str() {
83
            "overhand" => Ok(Self::Overhand),
84
            "underhand" => Ok(Self::Underhand),
85
            _ => Err(()),
86
        }
87
    }
88
}
89
90
impl Run {
91
    pub fn delete_foreskin(&mut self, if_match: Option<&str>) -> Result<Outcome, Problem> {
92
        if self.body.foreskin.is_none() {
93
            return Err(Problem::new(
94
                StatusCode::GONE,
95
                "foreskin-gone",
96
                "The foreskin is gone. It will not be returning.",
97
            ));
98
        }
99
        let Some(if_match) = if_match else {
100
            return Err(Problem::new(
101
                StatusCode::PRECONDITION_REQUIRED,
102
                "confirmation-required",
103
                "The server requires you to be sure. Send If-Match.",
104
            ));
105
        };
106
        if !etag_matches(if_match, &self.etag()) {
107
            return Err(Problem::new(
108
                StatusCode::PRECONDITION_FAILED,
109
                "stale-foreskin",
110
                "The foreskin has changed since you last looked at it.",
111
            ));
112
        }
113
        self.body.foreskin = None;
114
        self.standing.shame += 20;
115
        self.touch();
116
        Ok(Outcome::empty(StatusCode::NO_CONTENT))
117
    }
118
119
    pub fn set_grip(&mut self, grip: Grip) -> Result<(), Problem> {
120
        self.grip_closes(grip)?;
121
        self.body.grip = grip;
122
        self.touch();
123
        Ok(())
124
    }
125
126
    pub(super) fn grip_closes(&self, grip: Grip) -> Result<(), Problem> {
127
        if grip == Grip::Underhand && self.body.girth > GIRTH_THICK {
128
            return Err(Problem::new(
129
                StatusCode::PAYLOAD_TOO_LARGE,
130
                "grip-too-small",
131
                format!(
132
                    "The underhand grip no longer closes around {:.2} inches of girth.",
133
                    self.body.girth
134
                ),
135
            )
136
            .with("maximum", GIRTH_THICK));
137
        }
138
        Ok(())
139
    }
140
141
    /// Everyone in the changing room self-reports, so the comparison is self-report against self-report.
142
    pub fn holds_own_in_the_locker_room(&self) -> bool {
143
        self.self_reported() >= LOCKER_ROOM_BAR
144
    }
145
146
    pub fn girth_respected(&self) -> bool {
147
        self.body.girth >= GIRTH_RESPECTED
148
    }
149
}