an http based roguelike rpg about jelqing
Blame: src/game/mod.rs
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 1 | mod catalog; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 2 | mod encounters; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 3 | mod jelq; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 4 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 5 | pub use catalog::{LUBRICANTS, Lube, SHOP, ShopItem, Technique}; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 6 | pub use encounters::{ELDER_LAST_MODIFIED, FORUM_THREAD, PatchOp, forum_thread}; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 7 | |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 8 | use std::{ |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 9 | sync::LazyLock, |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 10 | time::{Duration, Instant, SystemTime}, |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 11 | }; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 12 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 13 | use axum::{ |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 14 | Json, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 15 | http::{HeaderName, HeaderValue, StatusCode, header}, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 16 | response::{IntoResponse, Response}, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 17 | }; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 18 | use rand::{Rng, SeedableRng, rngs::StdRng}; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 19 | use serde_json::{Value, json}; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 20 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 21 | use crate::problem::Problem; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 22 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 23 | pub const MAX_STAMINA: i32 = 10; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 24 | pub const SUSPICION_LIMIT: u8 = 5; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 25 | pub const BRANCH_TURN: u32 = 10; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 26 | pub const FLOOR_TURNS: u32 = 6; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 27 | pub const STARTING_CASH: u32 = 20; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 28 | pub const FLOOR_ALLOWANCE: u32 = 10; |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 29 | pub static REFRACTORY: LazyLock<Duration> = LazyLock::new(|| { |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 30 | let seconds = std::env::var("JELQD_REFRACTORY_SECS") |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 31 | .ok() |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 32 | .and_then(|s| s.parse().ok()) |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 33 | .unwrap_or(30); |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 34 | Duration::from_secs(seconds) |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 35 | }); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 36 | pub const SELF_REPORT_INFLATION: f64 = 1.5; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 37 | pub const MM_PER_INCH: f64 = 25.4; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 38 | pub const RUN_ALLOW: &str = "GET, HEAD, JELQ, PUT, PATCH, DELETE, OPTIONS, TRACE"; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 39 | pub const CLINIC_ALLOW: &str = "GET, HEAD, PUT, PATCH, DELETE, OPTIONS, TRACE"; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 40 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 41 | pub const SERVER_TIMING: HeaderName = HeaderName::from_static("server-timing"); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 42 | pub const X_DIAGNOSIS: HeaderName = HeaderName::from_static("x-diagnosis"); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 43 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 44 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 45 | pub enum Floor { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 46 | Bedroom, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 47 | Bathroom, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 48 | LockerRoom, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 49 | Grandmas, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 50 | Hotel, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 51 | Forum, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 52 | Clinic, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 53 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 54 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 55 | impl Floor { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 56 | const ALL: [Self; 7] = [ |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 57 | Self::Bedroom, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 58 | Self::Bathroom, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 59 | Self::LockerRoom, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 60 | Self::Grandmas, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 61 | Self::Hotel, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 62 | Self::Forum, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 63 | Self::Clinic, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 64 | ]; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 65 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 66 | pub fn number(self) -> usize { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 67 | Self::ALL.iter().position(|f| *f == self).unwrap_or(0) + 1 |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 68 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 69 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 70 | fn next(self) -> Option<Self> { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 71 | Self::ALL.get(self.number()).copied() |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 72 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 73 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 74 | pub fn name(self) -> &'static str { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 75 | match self { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 76 | Self::Bedroom => "the bedroom", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 77 | Self::Bathroom => "the shared bathroom", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 78 | Self::LockerRoom => "the gym locker room", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 79 | Self::Grandmas => "grandma's house for Thanksgiving", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 80 | Self::Hotel => "a business trip hotel", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 81 | Self::Forum => "the forum", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 82 | Self::Clinic => "the urologist's office", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 83 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 84 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 85 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 86 | pub fn note(self) -> &'static str { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 87 | match self { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 88 | Self::Bedroom => "The door does not lock. The cat is present.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 89 | Self::Bathroom => "The water is hot. The roommate showers at eight.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 90 | Self::LockerRoom => { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 91 | "It is cold. Discretion is advised. Everyone here is lying. Pump-Bro is by the lockers." |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 92 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 93 | Self::Grandmas => { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 94 | "The walls are thin. Mom is here, and she has been reading about Digest authentication." |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 95 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 96 | Self::Hotel => { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 97 | "The wifi requires agreement. Housekeeping does not knock. Pump-Bro is in the lobby." |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 98 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 99 | Self::Forum => "Nothing physical happens here. The Elder has a spreadsheet.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 100 | Self::Clinic => "The urologist will see you now. He is bored.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 101 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 102 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 103 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 104 | fn observer(self) -> Option<&'static str> { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 105 | match self { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 106 | Self::Bedroom => Some("cat"), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 107 | Self::LockerRoom => Some("greg"), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 108 | Self::Grandmas => Some("grandma"), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 109 | _ => None, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 110 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 111 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 112 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 113 | pub fn shop_open(self) -> bool { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 114 | matches!(self, Self::LockerRoom | Self::Hotel) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 115 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 116 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 117 | pub fn has_mom(self) -> bool { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 118 | matches!(self, Self::Bedroom | Self::Grandmas) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 119 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 120 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 121 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 122 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 123 | pub enum Grip { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 124 | Overhand, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 125 | Underhand, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 126 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 127 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 128 | impl Grip { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 129 | pub fn name(self) -> &'static str { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 130 | match self { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 131 | Self::Overhand => "overhand", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 132 | Self::Underhand => "underhand", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 133 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 134 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 135 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 136 | pub fn parse(protocol: &str) -> Option<Self> { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 137 | match protocol.to_ascii_lowercase().as_str() { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 138 | "overhand" => Some(Self::Overhand), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 139 | "underhand" => Some(Self::Underhand), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 140 | _ => None, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 141 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 142 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 143 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 144 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 145 | #[derive(Default)] |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 146 | pub struct Items { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 147 | pub ruler: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 148 | pub ice_packs: u32, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 149 | pub door_locks: u32, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 150 | pub dnd_sign: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 151 | pub pump_pro: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 152 | pub extender: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 153 | pub ring: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 154 | pub supplements: u32, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 155 | pub cage: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 156 | pub spreadsheet: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 157 | pub teapot_spout: bool, |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 158 | pub blinds: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 159 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 160 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 161 | enum Payload { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 162 | Empty, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 163 | Json(Value), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 164 | Raw(&'static str, Vec<u8>), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 165 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 166 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 167 | pub struct Outcome { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 168 | status: StatusCode, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 169 | payload: Payload, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 170 | headers: Vec<(HeaderName, String)>, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 171 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 172 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 173 | impl Outcome { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 174 | pub fn empty(status: StatusCode) -> Self { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 175 | Self { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 176 | status, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 177 | payload: Payload::Empty, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 178 | headers: Vec::new(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 179 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 180 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 181 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 182 | pub fn json(status: StatusCode, body: Value) -> Self { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 183 | Self { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 184 | payload: Payload::Json(body), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 185 | ..Self::empty(status) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 186 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 187 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 188 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 189 | pub fn raw(status: StatusCode, content_type: &'static str, body: impl Into<Vec<u8>>) -> Self { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 190 | Self { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 191 | payload: Payload::Raw(content_type, body.into()), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 192 | ..Self::empty(status) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 193 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 194 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 195 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 196 | pub fn header(mut self, name: HeaderName, value: impl Into<String>) -> Self { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 197 | self.headers.push((name, value.into())); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 198 | self |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 199 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 200 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 201 | pub fn status(&self) -> StatusCode { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 202 | self.status |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 203 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 204 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 205 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 206 | impl IntoResponse for Outcome { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 207 | fn into_response(self) -> Response { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 208 | let mut response = match self.payload { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 209 | // RFC 9110 §15.3.7 obliges a 206 to be a byte range of the full representation, so it is. |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 210 | Payload::Json(body) if self.status == StatusCode::PARTIAL_CONTENT => { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 211 | let full = body.to_string().into_bytes(); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 212 | let half = full.len() / 2; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 213 | let range = format!("bytes 0-{}/{}", half - 1, full.len()); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 214 | ( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 215 | self.status, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 216 | [ |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 217 | (header::CONTENT_TYPE, "application/json".to_owned()), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 218 | (header::CONTENT_RANGE, range), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 219 | ], |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 220 | full[..half].to_vec(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 221 | ) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 222 | .into_response() |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 223 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 224 | Payload::Json(body) => (self.status, Json(body)).into_response(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 225 | Payload::Raw(content_type, body) => { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 226 | (self.status, [(header::CONTENT_TYPE, content_type)], body).into_response() |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 227 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 228 | Payload::Empty => self.status.into_response(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 229 | }; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 230 | for (name, value) in self.headers { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 231 | let value = HeaderValue::from_str(&value).expect("header values are visible ascii"); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 232 | response.headers_mut().append(name, value); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 233 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 234 | response |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 235 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 236 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 237 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 238 | pub struct Pending { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 239 | due: u32, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 240 | length: f64, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 241 | girth: f64, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 242 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 243 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 244 | pub struct Ending { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 245 | pub cause: &'static str, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 246 | pub detail: String, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 247 | pub turn: u32, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 248 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 249 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 250 | pub struct Run { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 251 | pub id: String, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 252 | pub origin_etag: String, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 253 | pub version: u64, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 254 | pub turn: u32, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 255 | pub floor: Floor, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 256 | pub floor_turns: u32, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 257 | pub length: f64, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 258 | pub temp_length: f64, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 259 | pub girth: f64, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 260 | pub foreskin: Option<f64>, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 261 | pub eq: i32, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 262 | pub stamina: i32, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 263 | pub suspicion: u8, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 264 | pub shame_base: u32, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 265 | pub shame: u32, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 266 | pub cash: u32, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 267 | pub grip: Grip, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 268 | pub items: Items, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 269 | pub warmed_up: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 270 | pub teapot: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 271 | pub peyronie: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 272 | pub put_applied: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 273 | pub forum_read: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 274 | pub branch_pending: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 275 | pub mom_present: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 276 | pub mom_errand: &'static str, |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 277 | pub mom_strikes: u8, |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 278 | pub elder_strikes: u8, |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 279 | pub seeded: bool, |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 280 | pub towel_stolen: bool, |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 281 | pub pulled_muscle: bool, |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 282 | pub priapism_turns: u8, |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 283 | pub filmed_at: Option<SystemTime>, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 284 | pub observed: Option<&'static str>, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 285 | pub measured: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 286 | pub cured: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 287 | pub roommate_told: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 288 | pub wifi_accepted: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 289 | pub verified: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 290 | pub elder_beaten: bool, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 291 | pub edges: u8, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 292 | pub drawer_searches: u8, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 293 | pub intake_attempts: usize, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 294 | pub nonce: String, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 295 | pub heard: Vec<String>, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 296 | pub refractory_until: Option<Instant>, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 297 | pub pending: Vec<Pending>, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 298 | pub ended: Option<Ending>, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 299 | rng: StdRng, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 300 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 301 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 302 | pub fn round2(x: f64) -> f64 { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 303 | (x * 100.0).round() / 100.0 |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 304 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 305 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 306 | fn round3(x: f64) -> f64 { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 307 | (x * 1000.0).round() / 1000.0 |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 308 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 309 | |
177535ac8abf | atagen | 2026-10-10 10:39 UTC | 310 | fn honest(claimed: f64, actual: f64) -> bool { |
177535ac8abf | atagen | 2026-10-10 10:39 UTC | 311 | (claimed - actual).abs() < 0.06 |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 312 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 313 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 314 | fn not_extended() -> Problem { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 315 | Problem::new( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 316 | StatusCode::NOT_EXTENDED, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 317 | "not-extended", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 318 | "The requested extension was not applied. The server has recorded the attempt.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 319 | ) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 320 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 321 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 322 | pub fn parse_credentials<'a>(authorization: &'a str, scheme: &str) -> Option<&'a str> { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 323 | let (given, rest) = authorization.trim().split_once(' ')?; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 324 | given |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 325 | .eq_ignore_ascii_case(scheme) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 326 | .then(|| rest.trim().trim_matches('"')) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 327 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 328 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 329 | pub fn etag_matches(condition: &str, etag: &str) -> bool { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 330 | condition |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 331 | .split(',') |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 332 | .map(str::trim) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 333 | .any(|tag| tag == "*" || tag.trim_start_matches("W/") == etag) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 334 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 335 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 336 | pub fn shaft(mm: usize) -> String { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 337 | format!("8{}D", "=".repeat(mm.saturating_sub(2))) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 338 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 339 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 340 | impl Run { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 341 | pub fn new(id: String, seed: u64, shame_base: u32) -> Self { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 342 | let mut rng = StdRng::seed_from_u64(seed); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 343 | let length = round2(rng.random_range(4.5..6.5)); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 344 | let girth = round2(rng.random_range(4.0..5.0)); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 345 | let foreskin = round2(rng.random_range(0.4..2.4)); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 346 | let mut run = Self { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 347 | id, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 348 | origin_etag: String::new(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 349 | version: 0, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 350 | turn: 0, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 351 | floor: Floor::Bedroom, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 352 | floor_turns: 0, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 353 | length, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 354 | temp_length: 0.0, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 355 | girth, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 356 | foreskin: Some(foreskin), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 357 | eq: 20, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 358 | stamina: MAX_STAMINA, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 359 | suspicion: 0, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 360 | shame_base, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 361 | shame: 0, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 362 | cash: STARTING_CASH, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 363 | grip: Grip::Overhand, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 364 | items: Items { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 365 | ice_packs: 1, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 366 | ..Items::default() |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 367 | }, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 368 | warmed_up: false, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 369 | teapot: false, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 370 | peyronie: false, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 371 | put_applied: false, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 372 | forum_read: false, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 373 | branch_pending: false, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 374 | mom_present: false, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 375 | mom_errand: "She heard a noise.", |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 376 | mom_strikes: 0, |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 377 | elder_strikes: 0, |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 378 | seeded: false, |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 379 | towel_stolen: false, |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 380 | pulled_muscle: false, |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 381 | priapism_turns: 0, |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 382 | filmed_at: None, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 383 | observed: None, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 384 | measured: false, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 385 | cured: false, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 386 | roommate_told: false, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 387 | wifi_accepted: false, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 388 | verified: false, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 389 | elder_beaten: false, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 390 | edges: 0, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 391 | drawer_searches: 0, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 392 | intake_attempts: 0, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 393 | nonce: String::new(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 394 | heard: Vec::new(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 395 | refractory_until: None, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 396 | pending: Vec::new(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 397 | ended: None, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 398 | rng, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 399 | }; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 400 | run.origin_etag = run.etag(); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 401 | run.nonce = run.fresh_nonce(); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 402 | run |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 403 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 404 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 405 | pub fn etag(&self) -> String { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 406 | format!("\"{}-{}\"", self.id, self.version) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 407 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 408 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 409 | pub fn touch(&mut self) { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 410 | self.version += 1; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 411 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 412 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 413 | fn fresh_nonce(&mut self) -> String { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 414 | format!("{:016x}", self.rng.random::<u64>()) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 415 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 416 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 417 | pub fn current_length(&self) -> f64 { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 418 | self.length + self.temp_length |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 419 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 420 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 421 | pub fn self_reported(&self) -> f64 { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 422 | self.current_length() + SELF_REPORT_INFLATION |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 423 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 424 | |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 425 | pub fn max_stamina(&self) -> i32 { |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 426 | if self.pulled_muscle { 7 } else { MAX_STAMINA } |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 427 | } |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 428 | |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 429 | pub fn footage(&self) -> Option<(SystemTime, u64)> { |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 430 | let filmed = self.filmed_at?; |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 431 | let age = filmed.elapsed().map_or(0, |d| d.as_secs()); |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 432 | Some((filmed, age)) |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 433 | } |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 434 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 435 | pub fn total_shame(&self) -> u32 { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 436 | self.shame_base + self.shame |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 437 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 438 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 439 | pub fn status(&self) -> &'static str { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 440 | match (&self.ended, self.cured) { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 441 | (Some(_), _) => "gone", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 442 | (None, true) => "normal", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 443 | (None, false) => "alive", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 444 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 445 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 446 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 447 | pub fn view(&self) -> Value { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 448 | let conditions: Vec<&str> = [ |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 449 | (self.teapot, "short and stout"), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 450 | (self.peyronie, "curved"), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 451 | (self.items.cage, "caged"), |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 452 | (self.priapism_turns > 0, "priapism"), |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 453 | (self.pulled_muscle, "pulled muscle"), |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 454 | (self.filmed_at.is_some(), "viral"), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 455 | ] |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 456 | .into_iter() |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 457 | .filter_map(|(active, name)| active.then_some(name)) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 458 | .collect(); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 459 | let mut view = json!({ |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 460 | "id": self.id, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 461 | "status": self.status(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 462 | "turn": self.turn, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 463 | "floor": { "number": self.floor.number(), "name": self.floor.name() }, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 464 | "self_reported_length": round2(self.self_reported()), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 465 | "girth": round2(self.girth), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 466 | "foreskin": self.foreskin.map(round2), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 467 | "eq": self.eq, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 468 | "stamina": self.stamina, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 469 | "suspicion": self.suspicion, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 470 | "shame": self.total_shame(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 471 | "cash": self.cash, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 472 | "grip": self.grip.name(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 473 | "warmed_up": self.warmed_up, |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 474 | "ranked": !self.seeded, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 475 | "conditions": conditions, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 476 | }); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 477 | if self.items.ruler { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 478 | view["length"] = json!(round2(self.current_length())); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 479 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 480 | view |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 481 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 482 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 483 | pub fn floor_view(&self) -> Value { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 484 | let id = &self.id; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 485 | let turns_required = match self.floor { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 486 | Floor::Forum | Floor::Clinic => Value::Null, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 487 | _ => json!(FLOOR_TURNS), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 488 | }; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 489 | let mut view = json!({ |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 490 | "number": self.floor.number(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 491 | "name": self.floor.name(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 492 | "note": self.floor.note(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 493 | "turns": self.floor_turns, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 494 | "turns_required": turns_required, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 495 | "exit": { "href": format!("/runs/{id}/floor"), "method": "POST" }, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 496 | }); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 497 | match self.floor { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 498 | Floor::LockerRoom => { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 499 | view["others"] = json!([ |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 500 | { "name": "Greg", "self_reported_length": 9.5 }, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 501 | { "name": "Dmitri", "self_reported_length": 8.75 }, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 502 | { "name": "a man in a towel", "self_reported_length": 11.0 }, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 503 | ]); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 504 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 505 | Floor::Hotel => view["portal"] = json!(format!("/runs/{id}/portal")), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 506 | Floor::Forum => { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 507 | view["thread"] = json!(FORUM_THREAD); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 508 | view["elder"] = json!(format!("/runs/{id}/elder")); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 509 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 510 | Floor::Clinic => view["diagnosis"] = json!(format!("/runs/{id}/diagnosis")), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 511 | _ => {} |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 512 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 513 | if self.floor.shop_open() { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 514 | view["shop"] = json!(format!("/runs/{id}/shop")); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 515 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 516 | view |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 517 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 518 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 519 | pub fn inventory(&self) -> Vec<(&'static str, &'static str)> { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 520 | let i = &self.items; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 521 | let owned = [ |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 522 | ( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 523 | true, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 524 | "warm-towel", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 525 | "Reusable. Satisfies the warm-up precondition. Use technique=warmup.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 526 | ), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 527 | ( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 528 | i.ice_packs > 0, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 529 | "ice-pack", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 530 | "Lowers EQ by 40. Use technique=ice.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 531 | ), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 532 | ( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 533 | i.ruler, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 534 | "ruler", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 535 | "Measures honestly. The length resource now carries a Content-Length.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 536 | ), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 537 | ( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 538 | i.door_locks > 0, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 539 | "door-lock", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 540 | "Keeps the roommate out once.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 541 | ), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 542 | ( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 543 | i.dnd_sign, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 544 | "do-not-disturb-sign", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 545 | "Works on housekeeping. Mom does not read signs.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 546 | ), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 547 | ( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 548 | i.pump_pro, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 549 | "pump-pro", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 550 | "Use technique=pump. Gains are temporary.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 551 | ), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 552 | ( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 553 | i.extender, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 554 | "extender", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 555 | "Passive gains each turn. Takes up room. Can be removed with DELETE.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 556 | ), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 557 | ( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 558 | i.ring, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 559 | "cock-ring", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 560 | "Girth gains doubled. Rupture risk raised. Responses are compressed if you accept gzip. Can be removed with DELETE.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 561 | ), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 562 | (i.supplements > 0, "supplement", "Use technique=supplement."), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 563 | (i.cage, "chastity-cage", "Locked."), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 564 | ( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 565 | i.spreadsheet, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 566 | "spreadsheet", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 567 | "Charts your progress. Every line is flat.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 568 | ), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 569 | ( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 570 | i.teapot_spout, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 571 | "teapot-spout", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 572 | "Does nothing. Cannot be dropped.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 573 | ), |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 574 | ( |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 575 | i.blinds, |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 576 | "window-blinds", |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 577 | "The neighbour has a good camera. The blinds are better.", |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 578 | ), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 579 | ]; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 580 | owned |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 581 | .into_iter() |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 582 | .filter_map(|(have, slug, description)| have.then_some((slug, description))) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 583 | .collect() |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 584 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 585 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 586 | pub fn end(&mut self, cause: &'static str, detail: &str) { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 587 | self.ended = Some(Ending { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 588 | cause, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 589 | detail: detail.into(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 590 | turn: self.turn, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 591 | }); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 592 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 593 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 594 | fn check_refractory(&mut self, now: Instant) -> Result<(), Problem> { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 595 | let Some(until) = self.refractory_until else { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 596 | return Ok(()); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 597 | }; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 598 | if now < until { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 599 | let seconds = (until - now).as_secs() + 1; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 600 | return Err(Problem::new( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 601 | StatusCode::TOO_MANY_REQUESTS, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 602 | "overtrained", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 603 | "Stamina is exhausted. The refractory period is in effect.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 604 | ) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 605 | .header(header::RETRY_AFTER, seconds.to_string())); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 606 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 607 | self.refractory_until = None; |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 608 | self.stamina = self.max_stamina(); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 609 | self.eq = 20; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 610 | self.warmed_up = false; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 611 | self.touch(); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 612 | Ok(()) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 613 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 614 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 615 | fn realize(&mut self) { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 616 | let (due, waiting): (Vec<_>, Vec<_>) = std::mem::take(&mut self.pending) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 617 | .into_iter() |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 618 | .partition(|p| p.due <= self.turn); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 619 | self.pending = waiting; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 620 | for p in due { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 621 | self.length += p.length; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 622 | self.girth += p.girth; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 623 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 624 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 625 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 626 | fn gain(&mut self, length: f64, girth: f64, foreskin: f64) { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 627 | self.length += length; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 628 | self.girth += girth; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 629 | if let Some(f) = &mut self.foreskin { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 630 | *f += foreskin; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 631 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 632 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 633 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 634 | fn rupture(&mut self) -> Problem { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 635 | let detail = if self.items.ice_packs > 0 { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 636 | "A vessel has exceeded its rated pressure. The run has ended. An ice pack was available and was not used." |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 637 | } else { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 638 | "A vessel has exceeded its rated pressure. The run has ended." |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 639 | }; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 640 | self.end("vascular-event", detail); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 641 | Problem::new(StatusCode::INTERNAL_SERVER_ERROR, "vascular-event", detail) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 642 | .with("eq_at_failure", self.eq) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 643 | .with("turn", self.turn) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 644 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 645 | |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 646 | fn next_floor(&mut self, paid: bool) { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 647 | let Some(next) = self.floor.next() else { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 648 | return; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 649 | }; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 650 | self.floor = next; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 651 | self.floor_turns = 0; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 652 | self.temp_length = 0.0; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 653 | self.teapot = false; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 654 | self.warmed_up = false; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 655 | self.eq = 20; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 656 | self.suspicion = 0; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 657 | self.mom_present = false; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 658 | self.roommate_told = false; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 659 | self.drawer_searches = 0; |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 660 | self.towel_stolen = false; |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 661 | self.edges = 0; |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 662 | if paid { |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 663 | self.cash += FLOOR_ALLOWANCE; |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 664 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 665 | self.nonce = self.fresh_nonce(); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 666 | self.touch(); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 667 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 668 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 669 | pub fn advance(&mut self) -> Result<Outcome, Problem> { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 670 | match self.floor { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 671 | Floor::Clinic => { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 672 | return Err(Problem::new( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 673 | StatusCode::CONFLICT, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 674 | "no-further-floor", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 675 | "There is no further floor. There is only the diagnosis.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 676 | )); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 677 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 678 | Floor::Forum if !self.elder_beaten => { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 679 | return Err(Problem::new( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 680 | StatusCode::CONFLICT, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 681 | "elder-unaddressed", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 682 | "The Elder has not been addressed.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 683 | ) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 684 | .with("elder", format!("/runs/{}/elder", self.id))); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 685 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 686 | Floor::Forum => {} |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 687 | _ if self.floor_turns < FLOOR_TURNS => { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 688 | return Err(Problem::new( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 689 | StatusCode::TOO_EARLY, |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 690 | "floor-not-over", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 691 | format!( |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 692 | "The floor is not over. {} turns remain.", |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 693 | FLOOR_TURNS - self.floor_turns |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 694 | ), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 695 | )); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 696 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 697 | _ => {} |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 698 | } |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 699 | if self.rng.random_ratio(1, 120) { |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 700 | let detail = |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 701 | "The request conflicts with the current state of the zipper. The run has ended."; |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 702 | self.end("zipper", detail); |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 703 | return Err(Problem::new(StatusCode::CONFLICT, "zipper", detail)); |
c3e0c27cf8fd | atagen | 2026-10-10 11:28 UTC | 704 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 705 | let reverted = self.temp_length; |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 706 | self.next_floor(true); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 707 | let mut body = json!({ |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 708 | "detail": format!("You have arrived at {}.", self.floor.name()), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 709 | "floor": self.floor.number(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 710 | "note": self.floor.note(), |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 711 | }); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 712 | if reverted > 0.0 { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 713 | body["reverted"] = json!(round2(reverted)); |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 714 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 715 | Ok(Outcome::json(StatusCode::SEE_OTHER, body) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 716 | .header(header::LOCATION, format!("/runs/{}/floor", self.id))) |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 717 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 718 | } |