atagen

jelq

public

an http based roguelike rpg about jelqing

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