atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
fcc6cf6d5edcfdaf719c2b9cd40c6b77532340e6
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git

Blame: src/game/body.rs

782069860236atagen2026-10-10 12:44 UTC1
use axum::http::StatusCode;
782069860236atagen2026-10-10 12:44 UTC2
782069860236atagen2026-10-10 12:44 UTC3
use super::{
8b8e83329a1datagen2026-10-12 12:01 UTC4
    Outcome, Run,
8b8e83329a1datagen2026-10-12 12:01 UTC5
    balance::{GIRTH_RESPECTED, GIRTH_THICK, LOCKER_ROOM_BAR, MAX_STAMINA, STARTING_EQ},
8b8e83329a1datagen2026-10-12 12:01 UTC6
    credentials::etag_matches,
782069860236atagen2026-10-10 12:44 UTC7
};
782069860236atagen2026-10-10 12:44 UTC8
use crate::problem::Problem;
782069860236atagen2026-10-10 12:44 UTC9
8b8e83329a1datagen2026-10-12 12:01 UTC10
pub struct Body {
8b8e83329a1datagen2026-10-12 12:01 UTC11
    pub(crate) length: f64,
8b8e83329a1datagen2026-10-12 12:01 UTC12
    pub(crate) girth: f64,
8b8e83329a1datagen2026-10-12 12:01 UTC13
    pub(crate) foreskin: Option<f64>,
8b8e83329a1datagen2026-10-12 12:01 UTC14
    pub(crate) eq: i32,
8b8e83329a1datagen2026-10-12 12:01 UTC15
    pub(crate) stamina: i32,
8b8e83329a1datagen2026-10-12 12:01 UTC16
    pub(crate) friction: i32,
8b8e83329a1datagen2026-10-12 12:01 UTC17
    pub(crate) grip: Grip,
8b8e83329a1datagen2026-10-12 12:01 UTC18
    /// The grip of the last stroke, which decides whether this one gets the switching discount.
8b8e83329a1datagen2026-10-12 12:01 UTC19
    pub(crate) jelq_grip: Option<Grip>,
8b8e83329a1datagen2026-10-12 12:01 UTC20
    pub(crate) warmed_up: bool,
8b8e83329a1datagen2026-10-12 12:01 UTC21
    pub(crate) exhausted: bool,
8b8e83329a1datagen2026-10-12 12:01 UTC22
    pub(crate) put_applied: bool,
8b8e83329a1datagen2026-10-12 12:01 UTC23
    pub(crate) conditions: Conditions,
8b8e83329a1datagen2026-10-12 12:01 UTC24
    pub(crate) pending: Vec<Pending>,
fcc6cf6d5edcatagen2026-10-13 11:31 UTC25
    pub(crate) arrived: Option<Arrival>,
782069860236atagen2026-10-10 12:44 UTC26
}
782069860236atagen2026-10-10 12:44 UTC27
8b8e83329a1datagen2026-10-12 12:01 UTC28
impl Body {
8b8e83329a1datagen2026-10-12 12:01 UTC29
    pub fn new(length: f64, girth: f64, foreskin: f64) -> Self {
8b8e83329a1datagen2026-10-12 12:01 UTC30
        Self {
8b8e83329a1datagen2026-10-12 12:01 UTC31
            length,
8b8e83329a1datagen2026-10-12 12:01 UTC32
            girth,
8b8e83329a1datagen2026-10-12 12:01 UTC33
            foreskin: Some(foreskin),
8b8e83329a1datagen2026-10-12 12:01 UTC34
            eq: STARTING_EQ,
8b8e83329a1datagen2026-10-12 12:01 UTC35
            stamina: MAX_STAMINA,
8b8e83329a1datagen2026-10-12 12:01 UTC36
            friction: 0,
8b8e83329a1datagen2026-10-12 12:01 UTC37
            grip: Grip::Overhand,
8b8e83329a1datagen2026-10-12 12:01 UTC38
            jelq_grip: None,
8b8e83329a1datagen2026-10-12 12:01 UTC39
            warmed_up: false,
8b8e83329a1datagen2026-10-12 12:01 UTC40
            exhausted: false,
8b8e83329a1datagen2026-10-12 12:01 UTC41
            put_applied: false,
8b8e83329a1datagen2026-10-12 12:01 UTC42
            conditions: Conditions::default(),
8b8e83329a1datagen2026-10-12 12:01 UTC43
            pending: Vec::new(),
fcc6cf6d5edcatagen2026-10-13 11:31 UTC44
            arrived: None,
8b8e83329a1datagen2026-10-12 12:01 UTC45
        }
8b8e83329a1datagen2026-10-12 12:01 UTC46
    }
782069860236atagen2026-10-10 12:44 UTC47
}
782069860236atagen2026-10-10 12:44 UTC48
8b8e83329a1datagen2026-10-12 12:01 UTC49
#[derive(Default)]
8b8e83329a1datagen2026-10-12 12:01 UTC50
pub struct Conditions {
8b8e83329a1datagen2026-10-12 12:01 UTC51
    pub(crate) peyronie: bool,
8b8e83329a1datagen2026-10-12 12:01 UTC52
    pub(crate) pulled_muscle: bool,
8b8e83329a1datagen2026-10-12 12:01 UTC53
    pub(crate) priapism_turns: u8,
8b8e83329a1datagen2026-10-12 12:01 UTC54
    pub(crate) friction_burn: bool,
8b8e83329a1datagen2026-10-12 12:01 UTC55
}
8b8e83329a1datagen2026-10-12 12:01 UTC56
8b8e83329a1datagen2026-10-12 12:01 UTC57
/// Gains a `202` promised, due on a later turn.
8b8e83329a1datagen2026-10-12 12:01 UTC58
pub struct Pending {
8b8e83329a1datagen2026-10-12 12:01 UTC59
    pub(crate) due: u32,
8b8e83329a1datagen2026-10-12 12:01 UTC60
    pub(crate) length: f64,
8b8e83329a1datagen2026-10-12 12:01 UTC61
    pub(crate) girth: f64,
8b8e83329a1datagen2026-10-12 12:01 UTC62
    pub(crate) foreskin: f64,
8b8e83329a1datagen2026-10-12 12:01 UTC63
}
8b8e83329a1datagen2026-10-12 12:01 UTC64
fcc6cf6d5edcatagen2026-10-13 11:31 UTC65
#[derive(Default)]
fcc6cf6d5edcatagen2026-10-13 11:31 UTC66
pub struct Arrival {
fcc6cf6d5edcatagen2026-10-13 11:31 UTC67
    pub(crate) length: f64,
fcc6cf6d5edcatagen2026-10-13 11:31 UTC68
    pub(crate) girth: f64,
fcc6cf6d5edcatagen2026-10-13 11:31 UTC69
    pub(crate) foreskin: Option<f64>,
fcc6cf6d5edcatagen2026-10-13 11:31 UTC70
}
fcc6cf6d5edcatagen2026-10-13 11:31 UTC71
8b8e83329a1datagen2026-10-12 12:01 UTC72
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
8b8e83329a1datagen2026-10-12 12:01 UTC73
pub enum Grip {
8b8e83329a1datagen2026-10-12 12:01 UTC74
    Overhand,
8b8e83329a1datagen2026-10-12 12:01 UTC75
    Underhand,
8b8e83329a1datagen2026-10-12 12:01 UTC76
}
8b8e83329a1datagen2026-10-12 12:01 UTC77
8b8e83329a1datagen2026-10-12 12:01 UTC78
impl Grip {
8b8e83329a1datagen2026-10-12 12:01 UTC79
    pub fn name(self) -> &'static str {
8b8e83329a1datagen2026-10-12 12:01 UTC80
        match self {
8b8e83329a1datagen2026-10-12 12:01 UTC81
            Self::Overhand => "overhand",
8b8e83329a1datagen2026-10-12 12:01 UTC82
            Self::Underhand => "underhand",
782069860236atagen2026-10-10 12:44 UTC83
        }
782069860236atagen2026-10-10 12:44 UTC84
    }
8b8e83329a1datagen2026-10-12 12:01 UTC85
}
782069860236atagen2026-10-10 12:44 UTC86
8b8e83329a1datagen2026-10-12 12:01 UTC87
impl std::str::FromStr for Grip {
8b8e83329a1datagen2026-10-12 12:01 UTC88
    type Err = ();
8b8e83329a1datagen2026-10-12 12:01 UTC89
8b8e83329a1datagen2026-10-12 12:01 UTC90
    fn from_str(protocol: &str) -> Result<Self, ()> {
8b8e83329a1datagen2026-10-12 12:01 UTC91
        match protocol.to_ascii_lowercase().as_str() {
8b8e83329a1datagen2026-10-12 12:01 UTC92
            "overhand" => Ok(Self::Overhand),
8b8e83329a1datagen2026-10-12 12:01 UTC93
            "underhand" => Ok(Self::Underhand),
8b8e83329a1datagen2026-10-12 12:01 UTC94
            _ => Err(()),
782069860236atagen2026-10-10 12:44 UTC95
        }
782069860236atagen2026-10-10 12:44 UTC96
    }
8b8e83329a1datagen2026-10-12 12:01 UTC97
}
782069860236atagen2026-10-10 12:44 UTC98
8b8e83329a1datagen2026-10-12 12:01 UTC99
impl Run {
782069860236atagen2026-10-10 12:44 UTC100
    pub fn delete_foreskin(&mut self, if_match: Option<&str>) -> Result<Outcome, Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC101
        if self.body.foreskin.is_none() {
782069860236atagen2026-10-10 12:44 UTC102
            return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC103
                StatusCode::GONE,
782069860236atagen2026-10-10 12:44 UTC104
                "foreskin-gone",
782069860236atagen2026-10-10 12:44 UTC105
                "The foreskin is gone. It will not be returning.",
782069860236atagen2026-10-10 12:44 UTC106
            ));
782069860236atagen2026-10-10 12:44 UTC107
        }
782069860236atagen2026-10-10 12:44 UTC108
        let Some(if_match) = if_match else {
782069860236atagen2026-10-10 12:44 UTC109
            return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC110
                StatusCode::PRECONDITION_REQUIRED,
782069860236atagen2026-10-10 12:44 UTC111
                "confirmation-required",
782069860236atagen2026-10-10 12:44 UTC112
                "The server requires you to be sure. Send If-Match.",
782069860236atagen2026-10-10 12:44 UTC113
            ));
782069860236atagen2026-10-10 12:44 UTC114
        };
782069860236atagen2026-10-10 12:44 UTC115
        if !etag_matches(if_match, &self.etag()) {
782069860236atagen2026-10-10 12:44 UTC116
            return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC117
                StatusCode::PRECONDITION_FAILED,
782069860236atagen2026-10-10 12:44 UTC118
                "stale-foreskin",
782069860236atagen2026-10-10 12:44 UTC119
                "The foreskin has changed since you last looked at it.",
782069860236atagen2026-10-10 12:44 UTC120
            ));
782069860236atagen2026-10-10 12:44 UTC121
        }
8b8e83329a1datagen2026-10-12 12:01 UTC122
        self.body.foreskin = None;
8b8e83329a1datagen2026-10-12 12:01 UTC123
        self.standing.shame += 20;
782069860236atagen2026-10-10 12:44 UTC124
        self.touch();
782069860236atagen2026-10-10 12:44 UTC125
        Ok(Outcome::empty(StatusCode::NO_CONTENT))
782069860236atagen2026-10-10 12:44 UTC126
    }
782069860236atagen2026-10-10 12:44 UTC127
058650ec79d6atagen2026-10-12 10:49 UTC128
    pub fn set_grip(&mut self, grip: Grip) -> Result<(), Problem> {
058650ec79d6atagen2026-10-12 10:49 UTC129
        self.grip_closes(grip)?;
8b8e83329a1datagen2026-10-12 12:01 UTC130
        self.body.grip = grip;
782069860236atagen2026-10-10 12:44 UTC131
        self.touch();
058650ec79d6atagen2026-10-12 10:49 UTC132
        Ok(())
058650ec79d6atagen2026-10-12 10:49 UTC133
    }
058650ec79d6atagen2026-10-12 10:49 UTC134
058650ec79d6atagen2026-10-12 10:49 UTC135
    pub(super) fn grip_closes(&self, grip: Grip) -> Result<(), Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC136
        if grip == Grip::Underhand && self.body.girth > GIRTH_THICK {
058650ec79d6atagen2026-10-12 10:49 UTC137
            return Err(Problem::new(
058650ec79d6atagen2026-10-12 10:49 UTC138
                StatusCode::PAYLOAD_TOO_LARGE,
058650ec79d6atagen2026-10-12 10:49 UTC139
                "grip-too-small",
058650ec79d6atagen2026-10-12 10:49 UTC140
                format!(
058650ec79d6atagen2026-10-12 10:49 UTC141
                    "The underhand grip no longer closes around {:.2} inches of girth.",
8b8e83329a1datagen2026-10-12 12:01 UTC142
                    self.body.girth
058650ec79d6atagen2026-10-12 10:49 UTC143
                ),
058650ec79d6atagen2026-10-12 10:49 UTC144
            )
058650ec79d6atagen2026-10-12 10:49 UTC145
            .with("maximum", GIRTH_THICK));
058650ec79d6atagen2026-10-12 10:49 UTC146
        }
058650ec79d6atagen2026-10-12 10:49 UTC147
        Ok(())
058650ec79d6atagen2026-10-12 10:49 UTC148
    }
058650ec79d6atagen2026-10-12 10:49 UTC149
058650ec79d6atagen2026-10-12 10:49 UTC150
    /// Everyone in the changing room self-reports, so the comparison is self-report against self-report.
058650ec79d6atagen2026-10-12 10:49 UTC151
    pub fn holds_own_in_the_locker_room(&self) -> bool {
058650ec79d6atagen2026-10-12 10:49 UTC152
        self.self_reported() >= LOCKER_ROOM_BAR
058650ec79d6atagen2026-10-12 10:49 UTC153
    }
058650ec79d6atagen2026-10-12 10:49 UTC154
058650ec79d6atagen2026-10-12 10:49 UTC155
    pub fn girth_respected(&self) -> bool {
8b8e83329a1datagen2026-10-12 12:01 UTC156
        self.body.girth >= GIRTH_RESPECTED
058650ec79d6atagen2026-10-12 10:49 UTC157
    }
159f0998263batagen2026-10-12 11:01 UTC158
}