atagen

jelq

public

an http based roguelike rpg about jelqing

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