atagen

jelq

public

an http based roguelike rpg about jelqing

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

Blame: src/game/kitchen.rs

0a0bfee8329eatagen2026-10-11 05:11 UTC1
use axum::http::{StatusCode, header};
0a0bfee8329eatagen2026-10-11 05:11 UTC2
use serde_json::json;
0a0bfee8329eatagen2026-10-11 05:11 UTC3
0a0bfee8329eatagen2026-10-11 05:11 UTC4
use super::{
0a0bfee8329eatagen2026-10-11 05:11 UTC5
    Cause, Floor, Item, Outcome, Run, Story,
0a0bfee8329eatagen2026-10-11 05:11 UTC6
    balance::{
a6caa2f99786atagen2026-10-12 10:38 UTC7
        CAUGHT_MELONED, COCONUT_ODDS, ENERGY_ODDS, KETTLE_ODDS, KITCHEN_ICE_ODDS, KITCHEN_TELLINGS,
0a0bfee8329eatagen2026-10-11 05:11 UTC8
        KITCHEN_TOLD, MELON_SHAME, RUPTURE_LINE, SUSPICION_LIMIT, 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
0a0bfee8329eatagen2026-10-11 05:11 UTC13
impl Run {
0a0bfee8329eatagen2026-10-11 05:11 UTC14
    /// Raid the fridge. It costs the floor turn like anything else, and the household keeps count.
0a0bfee8329eatagen2026-10-11 05:11 UTC15
    pub fn raid_kitchen(&mut self) -> Result<Outcome, Problem> {
0a0bfee8329eatagen2026-10-11 05:11 UTC16
        if !self.floor.has_kitchen() {
0a0bfee8329eatagen2026-10-11 05:11 UTC17
            return Err(Problem::new(
0a0bfee8329eatagen2026-10-11 05:11 UTC18
                StatusCode::NOT_FOUND,
0a0bfee8329eatagen2026-10-11 05:11 UTC19
                "no-kitchen",
0a0bfee8329eatagen2026-10-11 05:11 UTC20
                "There is no kitchen on this floor.",
0a0bfee8329eatagen2026-10-11 05:11 UTC21
            ));
0a0bfee8329eatagen2026-10-11 05:11 UTC22
        }
0a0bfee8329eatagen2026-10-11 05:11 UTC23
        if self.kitchen_tellings >= KITCHEN_TELLINGS {
0a0bfee8329eatagen2026-10-11 05:11 UTC24
            return Err(Problem::new(
0a0bfee8329eatagen2026-10-11 05:11 UTC25
                StatusCode::GONE,
0a0bfee8329eatagen2026-10-11 05:11 UTC26
                "kitchen-off-limits",
0a0bfee8329eatagen2026-10-11 05:11 UTC27
                "The kitchen is off limits. You have been told twice.",
0a0bfee8329eatagen2026-10-11 05:11 UTC28
            ));
0a0bfee8329eatagen2026-10-11 05:11 UTC29
        }
0a0bfee8329eatagen2026-10-11 05:11 UTC30
        if self.story == Story::Pending {
0a0bfee8329eatagen2026-10-11 05:11 UTC31
            return Ok(self.branch_offer());
0a0bfee8329eatagen2026-10-11 05:11 UTC32
        }
0a0bfee8329eatagen2026-10-11 05:11 UTC33
        self.touch();
0a0bfee8329eatagen2026-10-11 05:11 UTC34
        self.turn += 1;
0a0bfee8329eatagen2026-10-11 05:11 UTC35
        self.here.turns += 1;
0a0bfee8329eatagen2026-10-11 05:11 UTC36
        self.realise();
3f9091557ec3atagen2026-10-11 11:43 UTC37
        if self.chance(
3f9091557ec3atagen2026-10-11 11:43 UTC38
            self.decision("rupture", "Does this raid of the fridge rupture something?"),
3f9091557ec3atagen2026-10-11 11:43 UTC39
            self.rupture_odds(),
3f9091557ec3atagen2026-10-11 11:43 UTC40
        ) {
b85a0d4536c3atagen2026-10-11 08:50 UTC41
            return Err(self.rupture());
b85a0d4536c3atagen2026-10-11 08:50 UTC42
        }
0a0bfee8329eatagen2026-10-11 05:11 UTC43
        let was_below = self.suspicion < SUSPICION_LIMIT;
a6caa2f99786atagen2026-10-12 10:38 UTC44
        // One draw of 120 keeps the marginals at the documented 1 in 3, 1 in 5, 1 in 8 and 1 in 6.
3f9091557ec3atagen2026-10-11 11:43 UTC45
        let loot = self.oracle.pick(
3f9091557ec3atagen2026-10-11 11:43 UTC46
            self.decision("loot", "What does the fridge give up on this raid?"),
3f9091557ec3atagen2026-10-11 11:43 UTC47
            &[
3f9091557ec3atagen2026-10-11 11:43 UTC48
                120 / COCONUT_ODDS,
3f9091557ec3atagen2026-10-11 11:43 UTC49
                120 / ENERGY_ODDS,
3f9091557ec3atagen2026-10-11 11:43 UTC50
                120 / KITCHEN_ICE_ODDS,
a6caa2f99786atagen2026-10-12 10:38 UTC51
                120 / KETTLE_ODDS,
a6caa2f99786atagen2026-10-12 10:38 UTC52
                120 - (120 / COCONUT_ODDS
a6caa2f99786atagen2026-10-12 10:38 UTC53
                    + 120 / ENERGY_ODDS
a6caa2f99786atagen2026-10-12 10:38 UTC54
                    + 120 / KITCHEN_ICE_ODDS
a6caa2f99786atagen2026-10-12 10:38 UTC55
                    + 120 / KETTLE_ODDS),
3f9091557ec3atagen2026-10-11 11:43 UTC56
            ],
3f9091557ec3atagen2026-10-11 11:43 UTC57
            &[
3f9091557ec3atagen2026-10-11 11:43 UTC58
                "A jar of coconut oil, half of it already gone.",
3f9091557ec3atagen2026-10-11 11:43 UTC59
                "An energy drink, cold and faintly alarming.",
3f9091557ec3atagen2026-10-11 11:43 UTC60
                "An ice pack, buried under the peas.",
a6caa2f99786atagen2026-10-12 10:38 UTC61
                "The kettle has just boiled.",
3f9091557ec3atagen2026-10-11 11:43 UTC62
                "Nothing. The fridge is ordinary.",
3f9091557ec3atagen2026-10-11 11:43 UTC63
            ],
3f9091557ec3atagen2026-10-11 11:43 UTC64
        );
3f9091557ec3atagen2026-10-11 11:43 UTC65
        let outcome = if self.eq > RUPTURE_LINE
3f9091557ec3atagen2026-10-11 11:43 UTC66
            && self.oracle.ratio(
3f9091557ec3atagen2026-10-11 11:43 UTC67
                self.decision(
3f9091557ec3atagen2026-10-11 11:43 UTC68
                    "watermelon",
3f9091557ec3atagen2026-10-11 11:43 UTC69
                    "Is there a watermelon in the fridge, exactly the right shape?",
3f9091557ec3atagen2026-10-11 11:43 UTC70
                ),
3f9091557ec3atagen2026-10-11 11:43 UTC71
                WATERMELON.0,
3f9091557ec3atagen2026-10-11 11:43 UTC72
                WATERMELON.1,
3f9091557ec3atagen2026-10-11 11:43 UTC73
            ) {
0a0bfee8329eatagen2026-10-11 05:11 UTC74
            self.here.melon_offered = true;
0a0bfee8329eatagen2026-10-11 05:11 UTC75
            self.melon_offer()
3f9091557ec3atagen2026-10-11 11:43 UTC76
        } else if loot == 0 {
0a0bfee8329eatagen2026-10-11 05:11 UTC77
            self.items.grant(Item::CoconutOil);
0a0bfee8329eatagen2026-10-11 05:11 UTC78
            Ok(Outcome::json(
0a0bfee8329eatagen2026-10-11 05:11 UTC79
                StatusCode::OK,
0a0bfee8329eatagen2026-10-11 05:11 UTC80
                json!({ "detail": "A jar of coconut oil, half of it already gone." }),
0a0bfee8329eatagen2026-10-11 05:11 UTC81
            ))
3f9091557ec3atagen2026-10-11 11:43 UTC82
        } else if loot == 1 {
0a0bfee8329eatagen2026-10-11 05:11 UTC83
            self.items.grant(Item::EnergyDrink);
0a0bfee8329eatagen2026-10-11 05:11 UTC84
            Ok(Outcome::json(
0a0bfee8329eatagen2026-10-11 05:11 UTC85
                StatusCode::OK,
0a0bfee8329eatagen2026-10-11 05:11 UTC86
                json!({ "detail": "An energy drink, cold and faintly alarming." }),
0a0bfee8329eatagen2026-10-11 05:11 UTC87
            ))
3f9091557ec3atagen2026-10-11 11:43 UTC88
        } else if loot == 2 {
0a0bfee8329eatagen2026-10-11 05:11 UTC89
            self.items.grant(Item::IcePack);
0a0bfee8329eatagen2026-10-11 05:11 UTC90
            Ok(Outcome::json(
0a0bfee8329eatagen2026-10-11 05:11 UTC91
                StatusCode::OK,
0a0bfee8329eatagen2026-10-11 05:11 UTC92
                json!({ "detail": "An ice pack, buried under the peas." }),
0a0bfee8329eatagen2026-10-11 05:11 UTC93
            ))
a6caa2f99786atagen2026-10-12 10:38 UTC94
        } else if loot == 3 {
a6caa2f99786atagen2026-10-12 10:38 UTC95
            self.here.kettle_boiled = true;
a6caa2f99786atagen2026-10-12 10:38 UTC96
            Ok(Outcome::json(
a6caa2f99786atagen2026-10-12 10:38 UTC97
                StatusCode::OK,
a6caa2f99786atagen2026-10-12 10:38 UTC98
                json!({ "detail": "The kettle has just boiled. The towel can be warmed once more." }),
a6caa2f99786atagen2026-10-12 10:38 UTC99
            ))
0a0bfee8329eatagen2026-10-11 05:11 UTC100
        } else {
0a0bfee8329eatagen2026-10-11 05:11 UTC101
            Ok(Outcome::empty(StatusCode::NO_CONTENT))
0a0bfee8329eatagen2026-10-11 05:11 UTC102
        };
0a0bfee8329eatagen2026-10-11 05:11 UTC103
        self.settle(was_below);
0a0bfee8329eatagen2026-10-11 05:11 UTC104
        let outcome = outcome?;
3f9091557ec3atagen2026-10-11 11:43 UTC105
        if self.chance(
3f9091557ec3atagen2026-10-11 11:43 UTC106
            self.decision(
3f9091557ec3atagen2026-10-11 11:43 UTC107
                "kitchen-told",
3f9091557ec3atagen2026-10-11 11:43 UTC108
                "Has the household had enough of the raiding?",
3f9091557ec3atagen2026-10-11 11:43 UTC109
            ),
3f9091557ec3atagen2026-10-11 11:43 UTC110
            KITCHEN_TOLD,
3f9091557ec3atagen2026-10-11 11:43 UTC111
        ) {
0a0bfee8329eatagen2026-10-11 05:11 UTC112
            self.kitchen_tellings += 1;
0a0bfee8329eatagen2026-10-11 05:11 UTC113
            if self.kitchen_tellings >= KITCHEN_TELLINGS {
e807266c323batagen2026-10-12 09:46 UTC114
                self.end_kitchen_visit();
0a0bfee8329eatagen2026-10-11 05:11 UTC115
                return Err(Problem::new(
0a0bfee8329eatagen2026-10-11 05:11 UTC116
                    StatusCode::GONE,
0a0bfee8329eatagen2026-10-11 05:11 UTC117
                    "kitchen-off-limits",
0a0bfee8329eatagen2026-10-11 05:11 UTC118
                    "The kitchen is off limits. You have been told twice.",
0a0bfee8329eatagen2026-10-11 05:11 UTC119
                ));
0a0bfee8329eatagen2026-10-11 05:11 UTC120
            }
0a0bfee8329eatagen2026-10-11 05:11 UTC121
            self.notices.push(if self.floor == Floor::Nans {
0a0bfee8329eatagen2026-10-11 05:11 UTC122
                "Stop raiding the fridge."
0a0bfee8329eatagen2026-10-11 05:11 UTC123
            } else {
0a0bfee8329eatagen2026-10-11 05:11 UTC124
                "Stop raiding the kitchen."
0a0bfee8329eatagen2026-10-11 05:11 UTC125
            });
0a0bfee8329eatagen2026-10-11 05:11 UTC126
        }
0a0bfee8329eatagen2026-10-11 05:11 UTC127
        Ok(outcome)
0a0bfee8329eatagen2026-10-11 05:11 UTC128
    }
0a0bfee8329eatagen2026-10-11 05:11 UTC129
0a0bfee8329eatagen2026-10-11 05:11 UTC130
    fn melon_offer(&self) -> Result<Outcome, Problem> {
0a0bfee8329eatagen2026-10-11 05:11 UTC131
        let commit = self.path("/kitchen/watermelon");
0a0bfee8329eatagen2026-10-11 05:11 UTC132
        Ok(Outcome::json(
0a0bfee8329eatagen2026-10-11 05:11 UTC133
            StatusCode::MULTIPLE_CHOICES,
0a0bfee8329eatagen2026-10-11 05:11 UTC134
            json!({
0a0bfee8329eatagen2026-10-11 05:11 UTC135
                "detail": "The fridge holds a watermelon. It is exactly the right shape.",
0a0bfee8329eatagen2026-10-11 05:11 UTC136
                "choices": [
0a0bfee8329eatagen2026-10-11 05:11 UTC137
                    { "href": commit, "method": "POST", "title": "Commit to the watermelon" },
0a0bfee8329eatagen2026-10-11 05:11 UTC138
                    { "href": self.path(""), "method": "GET", "title": "Close the fridge" },
0a0bfee8329eatagen2026-10-11 05:11 UTC139
                ],
0a0bfee8329eatagen2026-10-11 05:11 UTC140
            }),
0a0bfee8329eatagen2026-10-11 05:11 UTC141
        )
0a0bfee8329eatagen2026-10-11 05:11 UTC142
        .header(
0a0bfee8329eatagen2026-10-11 05:11 UTC143
            header::LINK,
0a0bfee8329eatagen2026-10-11 05:11 UTC144
            format!(
0a0bfee8329eatagen2026-10-11 05:11 UTC145
                "<{commit}>; rel=\"alternate\"; title=\"Commit to the watermelon\"; allow=\"POST\""
0a0bfee8329eatagen2026-10-11 05:11 UTC146
            ),
0a0bfee8329eatagen2026-10-11 05:11 UTC147
        ))
0a0bfee8329eatagen2026-10-11 05:11 UTC148
    }
0a0bfee8329eatagen2026-10-11 05:11 UTC149
0a0bfee8329eatagen2026-10-11 05:11 UTC150
    /// The watermelon. It empties EQ, keeps the gains, costs shame, and the household may notice.
0a0bfee8329eatagen2026-10-11 05:11 UTC151
    pub fn commit_melon(&mut self) -> Result<Outcome, Problem> {
0a0bfee8329eatagen2026-10-11 05:11 UTC152
        if !self.here.melon_offered {
0a0bfee8329eatagen2026-10-11 05:11 UTC153
            return Err(Problem::new(
0a0bfee8329eatagen2026-10-11 05:11 UTC154
                StatusCode::CONFLICT,
0a0bfee8329eatagen2026-10-11 05:11 UTC155
                "no-melon-on-offer",
0a0bfee8329eatagen2026-10-11 05:11 UTC156
                "There is no watermelon on offer.",
0a0bfee8329eatagen2026-10-11 05:11 UTC157
            ));
0a0bfee8329eatagen2026-10-11 05:11 UTC158
        }
0a0bfee8329eatagen2026-10-11 05:11 UTC159
        self.here.melon_offered = false;
14de3e1e8dc2atagen2026-10-11 06:19 UTC160
        self.reset_eq(0);
0a0bfee8329eatagen2026-10-11 05:11 UTC161
        self.shame += MELON_SHAME;
0a0bfee8329eatagen2026-10-11 05:11 UTC162
        self.touch();
3f9091557ec3atagen2026-10-11 11:43 UTC163
        if self.chance(
3f9091557ec3atagen2026-10-11 11:43 UTC164
            self.decision(
3f9091557ec3atagen2026-10-11 11:43 UTC165
                "caught-meloned",
3f9091557ec3atagen2026-10-11 11:43 UTC166
                "Is Mum standing in the doorway as you look up?",
3f9091557ec3atagen2026-10-11 11:43 UTC167
            ),
3f9091557ec3atagen2026-10-11 11:43 UTC168
            CAUGHT_MELONED,
3f9091557ec3atagen2026-10-11 11:43 UTC169
        ) {
0a0bfee8329eatagen2026-10-11 05:11 UTC170
            return Err(self.end(Cause::Meloned));
0a0bfee8329eatagen2026-10-11 05:11 UTC171
        }
0a0bfee8329eatagen2026-10-11 05:11 UTC172
        Ok(Outcome::json(
0a0bfee8329eatagen2026-10-11 05:11 UTC173
            StatusCode::OK,
0a0bfee8329eatagen2026-10-11 05:11 UTC174
            json!({
0a0bfee8329eatagen2026-10-11 05:11 UTC175
                "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 UTC176
            }),
0a0bfee8329eatagen2026-10-11 05:11 UTC177
        ))
0a0bfee8329eatagen2026-10-11 05:11 UTC178
    }
0a0bfee8329eatagen2026-10-11 05:11 UTC179
}