an http based roguelike rpg about jelqing
Blame: src/game/gauges.rs
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 1 | use std::time::Instant; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 2 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 3 | use serde_json::{Value, json}; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 4 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 5 | use super::{ |
e028a4184d00 | atagen | 2026-10-14 10:49 UTC | 6 | Arousal, Arrival, Elder, Excuse, Floor, Grip, Item, Observer, Run, Story, Technique, |
35d571ccadc4 | atagen | 2026-10-14 11:33 UTC | 7 | balance::{DANGER_EQ, FLOOR_LIMIT}, |
880b29a0c11e | atagen | 2026-10-13 09:27 UTC | 8 | techniques::{Training, Utility}, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 9 | }; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 10 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 11 | pub enum Shown { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 12 | Measured(f64), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 13 | SelfReported(f64), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 14 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 15 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 16 | pub struct Gauges { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 17 | pub(crate) length: Shown, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 18 | pub(crate) girth: f64, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 19 | pub(crate) foreskin: Option<f64>, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 20 | pub(crate) friction: i32, |
e028a4184d00 | atagen | 2026-10-14 10:49 UTC | 21 | pub(crate) arousal: Arousal, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 22 | pub(crate) stamina: i32, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 23 | pub(crate) max_stamina: i32, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 24 | pub(crate) suspicion: u8, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 25 | pub(crate) cash: u32, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 26 | pub(crate) floor: u8, |
35d571ccadc4 | atagen | 2026-10-14 11:33 UTC | 27 | pub(crate) turn: u32, |
35d571ccadc4 | atagen | 2026-10-14 11:33 UTC | 28 | pub(crate) floor_turns: u32, |
35d571ccadc4 | atagen | 2026-10-14 11:33 UTC | 29 | pub(crate) floor_limit: Option<u32>, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 30 | pub(crate) observed: Option<Observer>, |
886159fd0fe7 | atagen | 2026-10-13 12:48 UTC | 31 | pub(crate) mum_challenge: Option<String>, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 32 | pub(crate) warnings: Vec<&'static str>, |
fcc6cf6d5edc | atagen | 2026-10-13 11:31 UTC | 33 | pub(crate) arrived: Option<Arrival>, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 34 | pub(crate) ruler: bool, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 35 | pub(crate) ring: bool, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 36 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 37 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 38 | pub struct Reach { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 39 | pub(crate) foreskin: bool, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 40 | pub(crate) drawer: bool, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 41 | pub(crate) kitchen: bool, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 42 | pub(crate) thread: bool, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 43 | pub(crate) shop: bool, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 44 | pub(crate) portal: bool, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 45 | pub(crate) elder: bool, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 46 | pub(crate) footage: bool, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 47 | pub(crate) diagnosis: bool, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 48 | pub(crate) branch_pending: bool, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 49 | pub(crate) shopping: bool, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 50 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 51 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 52 | impl Run { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 53 | /// Drains the notices, so call it once per response. |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 54 | pub fn gauges(&mut self) -> Gauges { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 55 | let conditions = &self.body.conditions; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 56 | let mut warnings = Vec::new(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 57 | if conditions.pulled_muscle { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 58 | warnings.push("a muscle has been pulled"); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 59 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 60 | if conditions.friction_burn { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 61 | warnings.push("friction burn"); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 62 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 63 | warnings.extend(self.notices.drain(..).map(|notice| notice.text())); |
fcc6cf6d5edc | atagen | 2026-10-13 11:31 UTC | 64 | let arrived = self.body.arrived.take(); |
fcc6cf6d5edc | atagen | 2026-10-13 11:31 UTC | 65 | if arrived.is_some() { |
fcc6cf6d5edc | atagen | 2026-10-13 11:31 UTC | 66 | warnings.push("the queued gains have arrived"); |
fcc6cf6d5edc | atagen | 2026-10-13 11:31 UTC | 67 | } |
e028a4184d00 | atagen | 2026-10-14 10:49 UTC | 68 | if self.hotter_than(DANGER_EQ) && self.ended.is_none() { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 69 | warnings.push("it is turning purple"); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 70 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 71 | Gauges { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 72 | length: if self.items.kit.ruler { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 73 | Shown::Measured(self.current_length()) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 74 | } else { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 75 | Shown::SelfReported(self.self_reported()) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 76 | }, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 77 | girth: self.body.girth, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 78 | foreskin: self.body.foreskin, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 79 | friction: self.body.friction, |
e028a4184d00 | atagen | 2026-10-14 10:49 UTC | 80 | arousal: self.arousal(), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 81 | stamina: self.body.stamina, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 82 | max_stamina: self.max_stamina(), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 83 | suspicion: self.standing.suspicion, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 84 | cash: self.standing.cash, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 85 | floor: self.floor.number(), |
35d571ccadc4 | atagen | 2026-10-14 11:33 UTC | 86 | turn: self.turn, |
35d571ccadc4 | atagen | 2026-10-14 11:33 UTC | 87 | floor_turns: self.here.turns, |
35d571ccadc4 | atagen | 2026-10-14 11:33 UTC | 88 | floor_limit: self.floor.timed().then_some(FLOOR_LIMIT), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 89 | observed: self.observed, |
886159fd0fe7 | atagen | 2026-10-13 12:48 UTC | 90 | mum_challenge: self.mum_challenge(), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 91 | warnings, |
fcc6cf6d5edc | atagen | 2026-10-13 11:31 UTC | 92 | arrived, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 93 | ruler: self.items.kit.ruler, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 94 | ring: self.items.kit.ring, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 95 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 96 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 97 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 98 | pub fn reach(&self) -> Reach { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 99 | Reach { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 100 | foreskin: self.body.foreskin.is_some(), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 101 | drawer: self.drawer_worth_searching(), |
f16ab17c8ff6 | atagen | 2026-10-13 11:14 UTC | 102 | kitchen: self.floor.has_kitchen() && !self.here.kitchen.closed, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 103 | thread: self.thread_reachable(), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 104 | shop: self.floor.shop_open(), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 105 | portal: self.needs_portal(), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 106 | elder: self.floor == Floor::Forum && self.online.elder != Elder::Beaten, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 107 | footage: self.online.filmed_at.is_some(), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 108 | diagnosis: self.floor == Floor::Clinic, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 109 | branch_pending: self.online.story == Story::Pending, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 110 | shopping: self.here.shopping, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 111 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 112 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 113 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 114 | pub fn needs_portal(&self) -> bool { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 115 | self.floor == Floor::Hotel && !self.here.wifi_accepted |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 116 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 117 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 118 | /// Shame follows the player across runs in a signed cookie. Each response adds this run's new |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 119 | /// shame to whatever total the client presents, so two runs at once both count. |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 120 | pub fn settle_shame(&mut self, presented: Option<u32>) -> u32 { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 121 | let standing = &mut self.standing; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 122 | let total = |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 123 | presented.unwrap_or(standing.shame_total) + (standing.shame - standing.shame_reported); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 124 | standing.shame_reported = standing.shame; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 125 | standing.shame_total = total; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 126 | total |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 127 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 128 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 129 | pub fn trace(&mut self) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 130 | self.standing.shame += 1; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 131 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 132 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 133 | pub fn measured(&self) -> bool { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 134 | self.items.kit.ruler |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 135 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 136 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 137 | pub fn id(&self) -> &str { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 138 | &self.ident.id |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 139 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 140 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 141 | pub fn last_seen(&self) -> Instant { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 142 | self.ident.last_seen |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 143 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 144 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 145 | /// A request has arrived. Whoever was watching last time has looked away. |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 146 | pub fn seen(&mut self, now: Instant) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 147 | self.ident.last_seen = now; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 148 | self.observed = None; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 149 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 150 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 151 | pub fn foreskin(&self) -> Option<f64> { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 152 | self.body.foreskin |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 153 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 154 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 155 | pub fn grip(&self) -> Grip { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 156 | self.body.grip |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 157 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 158 | |
880b29a0c11e | atagen | 2026-10-13 09:27 UTC | 159 | pub fn knows(&self, technique: Technique) -> bool { |
880b29a0c11e | atagen | 2026-10-13 09:27 UTC | 160 | match technique { |
880b29a0c11e | atagen | 2026-10-13 09:27 UTC | 161 | Technique::Training(Training::Uli) => self.online.uli_taught, |
880b29a0c11e | atagen | 2026-10-13 09:27 UTC | 162 | Technique::Utility(Utility::Meditate) => self.online.meditation_taught, |
880b29a0c11e | atagen | 2026-10-13 09:27 UTC | 163 | _ => true, |
880b29a0c11e | atagen | 2026-10-13 09:27 UTC | 164 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 165 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 166 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 167 | pub fn holds(&self, item: Item) -> bool { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 168 | self.items.has(item) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 169 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 170 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 171 | pub fn inventory_view(&self) -> Value { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 172 | let items: Vec<Value> = self |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 173 | .items |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 174 | .owned() |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 175 | .into_iter() |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 176 | .map(|item| { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 177 | json!({ |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 178 | "name": item.slug(), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 179 | "href": self.path(&format!("/inventory/{}", item.slug())), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 180 | }) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 181 | }) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 182 | .collect(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 183 | json!({ |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 184 | "items": items, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 185 | "ice_packs": self.items.stores.ice_packs, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 186 | "supplements": self.items.stores.supplements, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 187 | "excuses": self.excuses().into_iter().map(Excuse::phrase).collect::<Vec<_>>(), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 188 | }) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 189 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 190 | } |