an http based roguelike rpg about jelqing
Blame: src/game/utility.rs
782069860236 | atagen | 2026-10-10 12:44 UTC | 1 | use std::{ |
782069860236 | atagen | 2026-10-10 12:44 UTC | 2 | ops::ControlFlow, |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 3 | time::{Duration, SystemTime}, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 4 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 5 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 6 | use axum::http::{StatusCode, header}; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 7 | use serde_json::json; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 8 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 9 | use super::{ |
bad9f5b8202f | atagen | 2026-10-14 09:23 UTC | 10 | Floor, Lube, Outcome, Run, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 11 | balance::{ |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 12 | EDGE_EQ, EDGE_GIRTH, EDGE_LOOP, ENERGY_STAMINA, FRICTION_ICE, HOTEL_STRESS, ICE_EQ, |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 13 | PRIAPISM_EQ, PRIAPISM_TURNS, PUMP_GAIN, SHOWER_EQ, SUPPLEMENT_WORKS, TOWELLESS_EQ, |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 14 | WARMUP_EQ, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 15 | }, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 16 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 17 | use crate::problem::Problem; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 18 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 19 | const WEEK: Duration = Duration::from_secs(7 * 24 * 60 * 60); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 20 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 21 | impl Run { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 22 | pub(super) fn warm_up(&mut self) -> Result<Outcome, Problem> { |
4b1f5f88827d | atagen | 2026-10-13 10:18 UTC | 23 | if self.here.towel_warmed && !self.here.kitchen.kettle_boiled { |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 24 | return Err(Problem::new( |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 25 | StatusCode::CONFLICT, |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 26 | "towel-cold", |
4b1f5f88827d | atagen | 2026-10-13 10:18 UTC | 27 | "The towel has gone cold. It warms once per floor, unless someone boils the kettle.", |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 28 | )); |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 29 | } |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 30 | if self.floor == Floor::Hotel |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 31 | && self.chance( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 32 | self.decision( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 33 | "hotel-stress", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 34 | "Has the hotel's plumbing given up for the week?", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 35 | ), |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 36 | HOTEL_STRESS, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 37 | ) |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 38 | { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 39 | return Err(Problem::new( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 40 | StatusCode::SERVICE_UNAVAILABLE, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 41 | "service-unavailable", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 42 | "Service is unavailable. The server estimates next week. The server is being pessimistic.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 43 | ) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 44 | .header( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 45 | header::RETRY_AFTER, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 46 | httpdate::fmt_http_date(SystemTime::now() + WEEK), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 47 | )); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 48 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 49 | let (boost, detail) = if self.floor == Floor::Bathroom { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 50 | ( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 51 | SHOWER_EQ, |
7d3fb63208bf | atagen | 2026-10-11 04:38 UTC | 52 | "Warmed up. The shower is hot. Your brother's will not be.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 53 | ) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 54 | } else if self.here.towel_stolen { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 55 | (TOWELLESS_EQ, "Warmed up, poorly. The cat has the towel.") |
782069860236 | atagen | 2026-10-10 12:44 UTC | 56 | } else { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 57 | (WARMUP_EQ, "Warmed up. The towel has cooled.") |
782069860236 | atagen | 2026-10-10 12:44 UTC | 58 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 59 | self.touch(); |
e807266c323b | atagen | 2026-10-12 09:46 UTC | 60 | self.step_away(); |
4b1f5f88827d | atagen | 2026-10-13 10:18 UTC | 61 | if self.here.towel_warmed { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 62 | self.here.kitchen.kettle_boiled = false; |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 63 | } |
4b1f5f88827d | atagen | 2026-10-13 10:18 UTC | 64 | self.here.towel_warmed = true; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 65 | self.body.eq = (self.body.eq + boost).min(100); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 66 | Ok(Outcome::json(StatusCode::OK, json!({ "detail": detail }))) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 67 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 68 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 69 | pub(super) fn ice(&mut self) -> Result<Outcome, Problem> { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 70 | if self.items.stores.ice_packs == 0 { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 71 | return Err(Problem::new( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 72 | StatusCode::FAILED_DEPENDENCY, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 73 | "no-ice", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 74 | "The ice technique depends on an ice pack. There are none.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 75 | )); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 76 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 77 | self.touch(); |
e807266c323b | atagen | 2026-10-12 09:46 UTC | 78 | self.step_away(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 79 | self.items.stores.ice_packs -= 1; |
e2727527c151 | atagen | 2026-10-13 12:38 UTC | 80 | let treated = std::mem::take(&mut self.body.conditions.friction_burn); |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 81 | let eased = self.body.friction > 0; |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 82 | self.shed(FRICTION_ICE); |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 83 | let priapism = self.body.conditions.priapism_turns > 0; |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 84 | let mut detail = Vec::new(); |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 85 | if treated { |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 86 | detail.push("The friction burn is treated."); |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 87 | } |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 88 | if eased { |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 89 | detail.push("Friction has eased."); |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 90 | } |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 91 | if priapism { |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 92 | if detail.is_empty() { |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 93 | detail.push("The ice has no effect."); |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 94 | } else { |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 95 | detail.push("EQ has not moved."); |
e2727527c151 | atagen | 2026-10-13 12:38 UTC | 96 | } |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 97 | detail.push("The condition persists."); |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 98 | } else { |
e2727527c151 | atagen | 2026-10-13 12:38 UTC | 99 | self.body.eq = (self.body.eq - ICE_EQ).max(0); |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 100 | detail.push("EQ has been reduced."); |
e2727527c151 | atagen | 2026-10-13 12:38 UTC | 101 | } |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 102 | detail.push("The ice pack is gone."); |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 103 | Ok(Outcome::json( |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 104 | StatusCode::OK, |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 105 | json!({ "detail": detail.join(" ") }), |
d3c4718d061e | atagen | 2026-10-14 09:03 UTC | 106 | )) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 107 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 108 | |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 109 | pub(super) fn energy(&mut self) -> Result<Outcome, Problem> { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 110 | if self.items.stores.energy_drinks == 0 { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 111 | return Err(Problem::new( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 112 | StatusCode::FAILED_DEPENDENCY, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 113 | "no-energy-drink", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 114 | "The energy technique depends on an energy drink.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 115 | )); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 116 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 117 | self.items.stores.energy_drinks -= 1; |
e807266c323b | atagen | 2026-10-12 09:46 UTC | 118 | self.step_away(); |
a11497af08aa | atagen | 2026-10-13 12:04 UTC | 119 | self.body.exhausted = false; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 120 | self.body.stamina = (self.body.stamina + ENERGY_STAMINA).min(self.max_stamina()); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 121 | self.touch(); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 122 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 123 | StatusCode::OK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 124 | json!({ "detail": "The energy arrives before the regret. Stamina +3." }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 125 | )) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 126 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 127 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 128 | pub(super) fn supplement(&mut self) -> Result<Outcome, Problem> { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 129 | if self.items.stores.supplements == 0 { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 130 | return Err(Problem::new( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 131 | StatusCode::FAILED_DEPENDENCY, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 132 | "no-supplement", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 133 | "The supplement technique depends on a supplement.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 134 | )); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 135 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 136 | self.items.stores.supplements -= 1; |
e807266c323b | atagen | 2026-10-12 09:46 UTC | 137 | self.step_away(); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 138 | self.touch(); |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 139 | if self.chance( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 140 | self.decision( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 141 | "supplement", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 142 | "Does the supplement from Pump-Bro actually work?", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 143 | ), |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 144 | SUPPLEMENT_WORKS, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 145 | ) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 146 | self.body.stamina = self.max_stamina(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 147 | self.body.conditions.priapism_turns = PRIAPISM_TURNS; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 148 | self.body.eq = self.body.eq.max(PRIAPISM_EQ); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 149 | return Ok(Outcome::json( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 150 | StatusCode::OK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 151 | json!({ "detail": "The upstream returned a valid response. It is still returning it. Stamina is fully restored." }), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 152 | )); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 153 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 154 | Err(Problem::new( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 155 | StatusCode::BAD_GATEWAY, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 156 | "bad-gateway", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 157 | "The upstream returned an invalid response.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 158 | ) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 159 | .with("upstream", "Pump-Bro")) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 160 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 161 | |
bad9f5b8202f | atagen | 2026-10-14 09:23 UTC | 162 | pub(super) fn edge(&mut self, lube: Option<Lube>) -> Result<Outcome, Problem> { |
bad9f5b8202f | atagen | 2026-10-14 09:23 UTC | 163 | self.take_lube(lube)?; |
bad9f5b8202f | atagen | 2026-10-14 09:23 UTC | 164 | self.chafe(lube, 5, false); |
bad9f5b8202f | atagen | 2026-10-14 09:23 UTC | 165 | self.commit(5, lube, 1)?; |
e807266c323b | atagen | 2026-10-12 09:46 UTC | 166 | if let ControlFlow::Break(interrupted) = self.hazards(5)? { |
e807266c323b | atagen | 2026-10-12 09:46 UTC | 167 | return Ok(interrupted); |
e807266c323b | atagen | 2026-10-12 09:46 UTC | 168 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 169 | self.here.edges += 1; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 170 | self.body.eq = (self.body.eq + EDGE_EQ).min(100); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 171 | self.body.girth += EDGE_GIRTH; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 172 | if self.here.edges >= EDGE_LOOP { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 173 | self.here.edges = 0; |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 174 | self.exhaust(); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 175 | return Err(Problem::new( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 176 | StatusCode::LOOP_DETECTED, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 177 | "edging-loop", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 178 | "Edging has looped. The server detected an infinite loop and terminated it.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 179 | )); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 180 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 181 | Ok(Outcome::json( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 182 | StatusCode::OK, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 183 | json!({ "detail": "Edged. EQ is rising. Nothing else is.", "edges": self.here.edges }), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 184 | )) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 185 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 186 | |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 187 | pub(super) fn pump(&mut self) -> Result<Outcome, Problem> { |
3ac624339c98 | atagen | 2026-10-13 12:21 UTC | 188 | self.commit(4, None, 1)?; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 189 | if let ControlFlow::Break(interrupted) = self.hazards(4)? { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 190 | return Ok(interrupted); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 191 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 192 | self.here.temp_length += PUMP_GAIN; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 193 | Ok(Outcome::json( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 194 | StatusCode::TEMPORARY_REDIRECT, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 195 | json!({ "detail": "Gains applied. They are temporary.", "length": PUMP_GAIN }), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 196 | ) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 197 | .header(header::LOCATION, self.path("/length"))) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 198 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 199 | } |