an http based roguelike rpg about jelqing
Blame: src/game/kitchen.rs
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 1 | use axum::http::{StatusCode, header}; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 2 | use serde_json::json; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 3 | |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 4 | use super::{ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 5 | Cause, Floor, Item, Outcome, Run, Story, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 6 | balance::{ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 7 | CAUGHT_MELONED, COCONUT_ODDS, ENERGY_ODDS, KITCHEN_ICE_ODDS, KITCHEN_TELLINGS, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 8 | KITCHEN_TOLD, MELON_SHAME, RUPTURE_LINE, SUSPICION_LIMIT, WATERMELON, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 9 | }, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 10 | }; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 11 | use crate::problem::Problem; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 12 | use rand::Rng; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 13 | |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 14 | impl Run { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 15 | /// Raid the fridge. It costs the floor turn like anything else, and the household keeps count. |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 16 | pub fn raid_kitchen(&mut self) -> Result<Outcome, Problem> { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 17 | if !self.floor.has_kitchen() { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 18 | return Err(Problem::new( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 19 | StatusCode::NOT_FOUND, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 20 | "no-kitchen", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 21 | "There is no kitchen on this floor.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 22 | )); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 23 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 24 | if self.kitchen_tellings >= KITCHEN_TELLINGS { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 25 | return Err(Problem::new( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 26 | StatusCode::GONE, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 27 | "kitchen-off-limits", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 28 | "The kitchen is off limits. You have been told twice.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 29 | )); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 30 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 31 | if self.story == Story::Pending { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 32 | return Ok(self.branch_offer()); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 33 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 34 | self.touch(); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 35 | self.turn += 1; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 36 | self.here.turns += 1; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 37 | self.realise(); |
b85a0d4536c3 | atagen | 2026-10-11 08:50 UTC | 38 | if self.chance(self.rupture_odds()) { |
b85a0d4536c3 | atagen | 2026-10-11 08:50 UTC | 39 | return Err(self.rupture()); |
b85a0d4536c3 | atagen | 2026-10-11 08:50 UTC | 40 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 41 | let was_below = self.suspicion < SUSPICION_LIMIT; |
139164489032 | atagen | 2026-10-11 05:46 UTC | 42 | // One draw of 120 keeps the marginals at the documented 1 in 3, 1 in 5 and 1 in 8. |
139164489032 | atagen | 2026-10-11 05:46 UTC | 43 | let draw = self.rng.random_range(0..120); |
139164489032 | atagen | 2026-10-11 05:46 UTC | 44 | let oil = 120 / COCONUT_ODDS; |
139164489032 | atagen | 2026-10-11 05:46 UTC | 45 | let energy = oil + 120 / ENERGY_ODDS; |
139164489032 | atagen | 2026-10-11 05:46 UTC | 46 | let ice = energy + 120 / KITCHEN_ICE_ODDS; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 47 | let outcome = if self.eq > RUPTURE_LINE && self.rng.random_ratio(WATERMELON.0, WATERMELON.1) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 48 | { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 49 | self.here.melon_offered = true; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 50 | self.melon_offer() |
139164489032 | atagen | 2026-10-11 05:46 UTC | 51 | } else if draw < oil { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 52 | self.items.grant(Item::CoconutOil); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 53 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 54 | StatusCode::OK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 55 | json!({ "detail": "A jar of coconut oil, half of it already gone." }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 56 | )) |
139164489032 | atagen | 2026-10-11 05:46 UTC | 57 | } else if draw < energy { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 58 | self.items.grant(Item::EnergyDrink); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 59 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 60 | StatusCode::OK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 61 | json!({ "detail": "An energy drink, cold and faintly alarming." }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 62 | )) |
139164489032 | atagen | 2026-10-11 05:46 UTC | 63 | } else if draw < ice { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 64 | self.items.grant(Item::IcePack); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 65 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 66 | StatusCode::OK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 67 | json!({ "detail": "An ice pack, buried under the peas." }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 68 | )) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 69 | } else { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 70 | Ok(Outcome::empty(StatusCode::NO_CONTENT)) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 71 | }; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 72 | self.settle(was_below); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 73 | let outcome = outcome?; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 74 | if self.chance(KITCHEN_TOLD) { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 75 | self.kitchen_tellings += 1; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 76 | if self.kitchen_tellings >= KITCHEN_TELLINGS { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 77 | return Err(Problem::new( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 78 | StatusCode::GONE, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 79 | "kitchen-off-limits", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 80 | "The kitchen is off limits. You have been told twice.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 81 | )); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 82 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 83 | self.notices.push(if self.floor == Floor::Nans { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 84 | "Stop raiding the fridge." |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 85 | } else { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 86 | "Stop raiding the kitchen." |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 87 | }); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 88 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 89 | Ok(outcome) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 90 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 91 | |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 92 | fn melon_offer(&self) -> Result<Outcome, Problem> { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 93 | let commit = self.path("/kitchen/watermelon"); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 94 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 95 | StatusCode::MULTIPLE_CHOICES, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 96 | json!({ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 97 | "detail": "The fridge holds a watermelon. It is exactly the right shape.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 98 | "choices": [ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 99 | { "href": commit, "method": "POST", "title": "Commit to the watermelon" }, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 100 | { "href": self.path(""), "method": "GET", "title": "Close the fridge" }, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 101 | ], |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 102 | }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 103 | ) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 104 | .header( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 105 | header::LINK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 106 | format!( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 107 | "<{commit}>; rel=\"alternate\"; title=\"Commit to the watermelon\"; allow=\"POST\"" |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 108 | ), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 109 | )) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 110 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 111 | |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 112 | /// The watermelon. It empties EQ, keeps the gains, costs shame, and the household may notice. |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 113 | pub fn commit_melon(&mut self) -> Result<Outcome, Problem> { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 114 | if !self.here.melon_offered { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 115 | return Err(Problem::new( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 116 | StatusCode::CONFLICT, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 117 | "no-melon-on-offer", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 118 | "There is no watermelon on offer.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 119 | )); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 120 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 121 | self.here.melon_offered = false; |
14de3e1e8dc2 | atagen | 2026-10-11 06:19 UTC | 122 | self.reset_eq(0); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 123 | self.shame += MELON_SHAME; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 124 | self.touch(); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 125 | if self.chance(CAUGHT_MELONED) { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 126 | return Err(self.end(Cause::Meloned)); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 127 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 128 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 129 | StatusCode::OK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 130 | json!({ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 131 | "detail": "The watermelon did not survive, and neither did the mood. EQ is gone, the gains are kept, and the shame is considerable.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 132 | }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 133 | )) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 134 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 135 | } |