atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
a043b8dc91b73012728c66725d8a23704ec419d1
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git

Blame: src/game/kitchen.rs

4f54028cba69atagen2026-10-14 11:45 UTC1
use axum::http::StatusCode;
0a0bfee8329eatagen2026-10-11 05:11 UTC2
use serde_json::json;
0a0bfee8329eatagen2026-10-11 05:11 UTC3
0a0bfee8329eatagen2026-10-11 05:11 UTC4
use super::{
4f54028cba69atagen2026-10-14 11:45 UTC5
    Cause, Choice, Floor, Item, Notice, Outcome, Run, Story,
0a0bfee8329eatagen2026-10-11 05:11 UTC6
    balance::{
f16ab17c8ff6atagen2026-10-13 11:14 UTC7
        CAUGHT_MELONED, COCONUT_ODDS, ENERGY_ODDS, KETTLE_ODDS, KITCHEN_ICE_ODDS, KITCHEN_TOLD,
f16ab17c8ff6atagen2026-10-13 11:14 UTC8
        MELON_SHAME, RAID_NOISE, RUPTURE_LINE, WATERMELON,
0a0bfee8329eatagen2026-10-11 05:11 UTC9
    },
0a0bfee8329eatagen2026-10-11 05:11 UTC10
};
0a0bfee8329eatagen2026-10-11 05:11 UTC11
use crate::problem::Problem;
0a0bfee8329eatagen2026-10-11 05:11 UTC12
4f54028cba69atagen2026-10-14 11:45 UTC13
#[derive(Clone, Copy)]
4f54028cba69atagen2026-10-14 11:45 UTC14
enum Loot {
4f54028cba69atagen2026-10-14 11:45 UTC15
    CoconutOil,
4f54028cba69atagen2026-10-14 11:45 UTC16
    EnergyDrink,
4f54028cba69atagen2026-10-14 11:45 UTC17
    IcePack,
4f54028cba69atagen2026-10-14 11:45 UTC18
    Kettle,
4f54028cba69atagen2026-10-14 11:45 UTC19
    Nothing,
4f54028cba69atagen2026-10-14 11:45 UTC20
}
4f54028cba69atagen2026-10-14 11:45 UTC21
4f54028cba69atagen2026-10-14 11:45 UTC22
impl Loot {
4f54028cba69atagen2026-10-14 11:45 UTC23
    const ALL: [Self; 5] = [
4f54028cba69atagen2026-10-14 11:45 UTC24
        Self::CoconutOil,
4f54028cba69atagen2026-10-14 11:45 UTC25
        Self::EnergyDrink,
4f54028cba69atagen2026-10-14 11:45 UTC26
        Self::IcePack,
4f54028cba69atagen2026-10-14 11:45 UTC27
        Self::Kettle,
4f54028cba69atagen2026-10-14 11:45 UTC28
        Self::Nothing,
4f54028cba69atagen2026-10-14 11:45 UTC29
    ];
4f54028cba69atagen2026-10-14 11:45 UTC30
4f54028cba69atagen2026-10-14 11:45 UTC31
    /// Out of 120, so one draw keeps the marginals at the documented 1 in 3, 1 in 5, 1 in 8 and 1 in 6.
4f54028cba69atagen2026-10-14 11:45 UTC32
    fn weight(self) -> u32 {
4f54028cba69atagen2026-10-14 11:45 UTC33
        match self {
4f54028cba69atagen2026-10-14 11:45 UTC34
            Self::CoconutOil => 120 / COCONUT_ODDS,
4f54028cba69atagen2026-10-14 11:45 UTC35
            Self::EnergyDrink => 120 / ENERGY_ODDS,
4f54028cba69atagen2026-10-14 11:45 UTC36
            Self::IcePack => 120 / KITCHEN_ICE_ODDS,
4f54028cba69atagen2026-10-14 11:45 UTC37
            Self::Kettle => 120 / KETTLE_ODDS,
4f54028cba69atagen2026-10-14 11:45 UTC38
            Self::Nothing => 120 - Self::ALL[..4].iter().map(|l| l.weight()).sum::<u32>(),
4f54028cba69atagen2026-10-14 11:45 UTC39
        }
4f54028cba69atagen2026-10-14 11:45 UTC40
    }
4f54028cba69atagen2026-10-14 11:45 UTC41
4f54028cba69atagen2026-10-14 11:45 UTC42
    fn sight(self) -> &'static str {
4f54028cba69atagen2026-10-14 11:45 UTC43
        match self {
4f54028cba69atagen2026-10-14 11:45 UTC44
            Self::CoconutOil => "A jar of coconut oil, half of it already gone.",
4f54028cba69atagen2026-10-14 11:45 UTC45
            Self::EnergyDrink => "An energy drink, cold and faintly alarming.",
4f54028cba69atagen2026-10-14 11:45 UTC46
            Self::IcePack => "An ice pack, buried under the peas.",
4f54028cba69atagen2026-10-14 11:45 UTC47
            Self::Kettle => "The kettle has just boiled.",
4f54028cba69atagen2026-10-14 11:45 UTC48
            Self::Nothing => "Nothing. The fridge is ordinary.",
4f54028cba69atagen2026-10-14 11:45 UTC49
        }
4f54028cba69atagen2026-10-14 11:45 UTC50
    }
4f54028cba69atagen2026-10-14 11:45 UTC51
}
4f54028cba69atagen2026-10-14 11:45 UTC52
0a0bfee8329eatagen2026-10-11 05:11 UTC53
impl Run {
c4d70621d377atagen2026-10-12 11:56 UTC54
    /// The household keeps count, so every raid makes noise and the noise makes the next telling likelier.
0a0bfee8329eatagen2026-10-11 05:11 UTC55
    pub fn raid_kitchen(&mut self) -> Result<Outcome, Problem> {
0a0bfee8329eatagen2026-10-11 05:11 UTC56
        if !self.floor.has_kitchen() {
0a0bfee8329eatagen2026-10-11 05:11 UTC57
            return Err(Problem::new(
0a0bfee8329eatagen2026-10-11 05:11 UTC58
                StatusCode::NOT_FOUND,
0a0bfee8329eatagen2026-10-11 05:11 UTC59
                "no-kitchen",
0a0bfee8329eatagen2026-10-11 05:11 UTC60
                "There is no kitchen on this floor.",
0a0bfee8329eatagen2026-10-11 05:11 UTC61
            ));
0a0bfee8329eatagen2026-10-11 05:11 UTC62
        }
f16ab17c8ff6atagen2026-10-13 11:14 UTC63
        if self.here.kitchen.closed {
f16ab17c8ff6atagen2026-10-13 11:14 UTC64
            return Err(self.off_limits());
0a0bfee8329eatagen2026-10-11 05:11 UTC65
        }
8b8e83329a1datagen2026-10-12 12:01 UTC66
        if self.online.story == Story::Pending {
0a0bfee8329eatagen2026-10-11 05:11 UTC67
            return Ok(self.branch_offer());
0a0bfee8329eatagen2026-10-11 05:11 UTC68
        }
3ac624339c98atagen2026-10-13 12:21 UTC69
        self.begin_turn()?;
3f9091557ec3atagen2026-10-11 11:43 UTC70
        if self.chance(
3f9091557ec3atagen2026-10-11 11:43 UTC71
            self.decision("rupture", "Does this raid of the fridge rupture something?"),
3f9091557ec3atagen2026-10-11 11:43 UTC72
            self.rupture_odds(),
3f9091557ec3atagen2026-10-11 11:43 UTC73
        ) {
b85a0d4536c3atagen2026-10-11 08:50 UTC74
            return Err(self.rupture());
b85a0d4536c3atagen2026-10-11 08:50 UTC75
        }
c4d70621d377atagen2026-10-12 11:56 UTC76
        self.make_noise(RAID_NOISE);
4f54028cba69atagen2026-10-14 11:45 UTC77
        let loot = Loot::ALL[self.oracle.pick(
3f9091557ec3atagen2026-10-11 11:43 UTC78
            self.decision("loot", "What does the fridge give up on this raid?"),
4f54028cba69atagen2026-10-14 11:45 UTC79
            &Loot::ALL.map(Loot::weight),
4f54028cba69atagen2026-10-14 11:45 UTC80
            &Loot::ALL.map(Loot::sight),
4f54028cba69atagen2026-10-14 11:45 UTC81
        )];
8b8e83329a1datagen2026-10-12 12:01 UTC82
        let melon = self.body.eq > RUPTURE_LINE
3f9091557ec3atagen2026-10-11 11:43 UTC83
            && self.oracle.ratio(
3f9091557ec3atagen2026-10-11 11:43 UTC84
                self.decision(
3f9091557ec3atagen2026-10-11 11:43 UTC85
                    "watermelon",
3f9091557ec3atagen2026-10-11 11:43 UTC86
                    "Is there a watermelon in the fridge, exactly the right shape?",
3f9091557ec3atagen2026-10-11 11:43 UTC87
                ),
3f9091557ec3atagen2026-10-11 11:43 UTC88
                WATERMELON.0,
3f9091557ec3atagen2026-10-11 11:43 UTC89
                WATERMELON.1,
c4d70621d377atagen2026-10-12 11:56 UTC90
            );
8b8e83329a1datagen2026-10-12 12:01 UTC91
        self.settle();
c4d70621d377atagen2026-10-12 11:56 UTC92
        if self.oracle.ratio(
3f9091557ec3atagen2026-10-11 11:43 UTC93
            self.decision(
3f9091557ec3atagen2026-10-11 11:43 UTC94
                "kitchen-told",
3f9091557ec3atagen2026-10-11 11:43 UTC95
                "Has the household had enough of the raiding?",
3f9091557ec3atagen2026-10-11 11:43 UTC96
            ),
8b8e83329a1datagen2026-10-12 12:01 UTC97
            u32::from(self.standing.suspicion) + 1,
3f9091557ec3atagen2026-10-11 11:43 UTC98
            KITCHEN_TOLD,
3f9091557ec3atagen2026-10-11 11:43 UTC99
        ) {
f16ab17c8ff6atagen2026-10-13 11:14 UTC100
            self.here.kitchen.closed = true;
f16ab17c8ff6atagen2026-10-13 11:14 UTC101
            self.end_kitchen_visit();
aef6f6498ea2atagen2026-10-13 11:23 UTC102
            self.notices.push(if self.floor == Floor::Nans {
aef6f6498ea2atagen2026-10-13 11:23 UTC103
                Notice::StopRaidingTheFridge
aef6f6498ea2atagen2026-10-13 11:23 UTC104
            } else {
aef6f6498ea2atagen2026-10-13 11:23 UTC105
                Notice::StopRaidingTheKitchen
aef6f6498ea2atagen2026-10-13 11:23 UTC106
            });
f16ab17c8ff6atagen2026-10-13 11:14 UTC107
            return Err(self.off_limits());
0a0bfee8329eatagen2026-10-11 05:11 UTC108
        }
c4d70621d377atagen2026-10-12 11:56 UTC109
        if melon {
8b8e83329a1datagen2026-10-12 12:01 UTC110
            self.here.kitchen.melon_offered = true;
c4d70621d377atagen2026-10-12 11:56 UTC111
            return Ok(self.melon_offer());
c4d70621d377atagen2026-10-12 11:56 UTC112
        }
c4d70621d377atagen2026-10-12 11:56 UTC113
        Ok(self.unpack(loot))
0a0bfee8329eatagen2026-10-11 05:11 UTC114
    }
0a0bfee8329eatagen2026-10-11 05:11 UTC115
f16ab17c8ff6atagen2026-10-13 11:14 UTC116
    fn off_limits(&self) -> Problem {
aef6f6498ea2atagen2026-10-13 11:23 UTC117
        Problem::new(
aef6f6498ea2atagen2026-10-13 11:23 UTC118
            StatusCode::GONE,
aef6f6498ea2atagen2026-10-13 11:23 UTC119
            "kitchen-off-limits",
aef6f6498ea2atagen2026-10-13 11:23 UTC120
            "The kitchen is off limits.",
aef6f6498ea2atagen2026-10-13 11:23 UTC121
        )
f16ab17c8ff6atagen2026-10-13 11:14 UTC122
    }
f16ab17c8ff6atagen2026-10-13 11:14 UTC123
4f54028cba69atagen2026-10-14 11:45 UTC124
    fn unpack(&mut self, loot: Loot) -> Outcome {
4f54028cba69atagen2026-10-14 11:45 UTC125
        let item = match loot {
4f54028cba69atagen2026-10-14 11:45 UTC126
            Loot::CoconutOil => Item::CoconutOil,
4f54028cba69atagen2026-10-14 11:45 UTC127
            Loot::EnergyDrink => Item::EnergyDrink,
4f54028cba69atagen2026-10-14 11:45 UTC128
            Loot::IcePack => Item::IcePack,
4f54028cba69atagen2026-10-14 11:45 UTC129
            Loot::Kettle => {
8b8e83329a1datagen2026-10-12 12:01 UTC130
                self.here.kitchen.kettle_boiled = true;
c4d70621d377atagen2026-10-12 11:56 UTC131
                return Outcome::json(
c4d70621d377atagen2026-10-12 11:56 UTC132
                    StatusCode::OK,
4f54028cba69atagen2026-10-14 11:45 UTC133
                    json!({ "detail": format!("{} The towel can be warmed once more.", loot.sight()) }),
c4d70621d377atagen2026-10-12 11:56 UTC134
                );
c4d70621d377atagen2026-10-12 11:56 UTC135
            }
4f54028cba69atagen2026-10-14 11:45 UTC136
            Loot::Nothing => return Outcome::empty(StatusCode::NO_CONTENT),
c4d70621d377atagen2026-10-12 11:56 UTC137
        };
c4d70621d377atagen2026-10-12 11:56 UTC138
        self.items.grant(item);
4f54028cba69atagen2026-10-14 11:45 UTC139
        Outcome::json(StatusCode::OK, json!({ "detail": loot.sight() }))
c4d70621d377atagen2026-10-12 11:56 UTC140
    }
c4d70621d377atagen2026-10-12 11:56 UTC141
c4d70621d377atagen2026-10-12 11:56 UTC142
    fn melon_offer(&self) -> Outcome {
4f54028cba69atagen2026-10-14 11:45 UTC143
        Outcome::choices(
4f54028cba69atagen2026-10-14 11:45 UTC144
            "The fridge holds a watermelon. It is exactly the right shape.",
4f54028cba69atagen2026-10-14 11:45 UTC145
            &[
4f54028cba69atagen2026-10-14 11:45 UTC146
                Choice {
4f54028cba69atagen2026-10-14 11:45 UTC147
                    href: self.path("/kitchen/watermelon"),
4f54028cba69atagen2026-10-14 11:45 UTC148
                    method: "POST",
4f54028cba69atagen2026-10-14 11:45 UTC149
                    title: "Commit to the watermelon",
4f54028cba69atagen2026-10-14 11:45 UTC150
                },
4f54028cba69atagen2026-10-14 11:45 UTC151
                Choice {
4f54028cba69atagen2026-10-14 11:45 UTC152
                    href: self.path(""),
4f54028cba69atagen2026-10-14 11:45 UTC153
                    method: "GET",
4f54028cba69atagen2026-10-14 11:45 UTC154
                    title: "Close the fridge",
4f54028cba69atagen2026-10-14 11:45 UTC155
                },
4f54028cba69atagen2026-10-14 11:45 UTC156
            ],
c4d70621d377atagen2026-10-12 11:56 UTC157
        )
0a0bfee8329eatagen2026-10-11 05:11 UTC158
    }
0a0bfee8329eatagen2026-10-11 05:11 UTC159
c4d70621d377atagen2026-10-12 11:56 UTC160
    /// A bail-out that costs no turn. The more the household has already heard, the likelier Mum is in the doorway.
0a0bfee8329eatagen2026-10-11 05:11 UTC161
    pub fn commit_melon(&mut self) -> Result<Outcome, Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC162
        if !self.here.kitchen.melon_offered {
0a0bfee8329eatagen2026-10-11 05:11 UTC163
            return Err(Problem::new(
0a0bfee8329eatagen2026-10-11 05:11 UTC164
                StatusCode::CONFLICT,
0a0bfee8329eatagen2026-10-11 05:11 UTC165
                "no-melon-on-offer",
0a0bfee8329eatagen2026-10-11 05:11 UTC166
                "There is no watermelon on offer.",
0a0bfee8329eatagen2026-10-11 05:11 UTC167
            ));
0a0bfee8329eatagen2026-10-11 05:11 UTC168
        }
8b8e83329a1datagen2026-10-12 12:01 UTC169
        self.here.kitchen.melon_offered = false;
14de3e1e8dc2atagen2026-10-11 06:19 UTC170
        self.reset_eq(0);
8b8e83329a1datagen2026-10-12 12:01 UTC171
        self.standing.shame += MELON_SHAME;
0a0bfee8329eatagen2026-10-11 05:11 UTC172
        self.touch();
c4d70621d377atagen2026-10-12 11:56 UTC173
        if self.oracle.ratio(
3f9091557ec3atagen2026-10-11 11:43 UTC174
            self.decision(
3f9091557ec3atagen2026-10-11 11:43 UTC175
                "caught-meloned",
3f9091557ec3atagen2026-10-11 11:43 UTC176
                "Is Mum standing in the doorway as you look up?",
3f9091557ec3atagen2026-10-11 11:43 UTC177
            ),
8b8e83329a1datagen2026-10-12 12:01 UTC178
            u32::from(self.standing.suspicion) + 2,
3f9091557ec3atagen2026-10-11 11:43 UTC179
            CAUGHT_MELONED,
3f9091557ec3atagen2026-10-11 11:43 UTC180
        ) {
0a0bfee8329eatagen2026-10-11 05:11 UTC181
            return Err(self.end(Cause::Meloned));
0a0bfee8329eatagen2026-10-11 05:11 UTC182
        }
0a0bfee8329eatagen2026-10-11 05:11 UTC183
        Ok(Outcome::json(
0a0bfee8329eatagen2026-10-11 05:11 UTC184
            StatusCode::OK,
0a0bfee8329eatagen2026-10-11 05:11 UTC185
            json!({
0a0bfee8329eatagen2026-10-11 05:11 UTC186
                "detail": "The watermelon did not survive, and neither did the mood. EQ is gone, the gains are kept, and the shame is considerable.",
0a0bfee8329eatagen2026-10-11 05:11 UTC187
            }),
0a0bfee8329eatagen2026-10-11 05:11 UTC188
        ))
0a0bfee8329eatagen2026-10-11 05:11 UTC189
    }
0a0bfee8329eatagen2026-10-11 05:11 UTC190
}