atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
1a144ee92b627d87d2ff575eded19a9b8baa3fe1
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/game/kitchen.rsRust190 lines6.2 KB
1
use axum::http::StatusCode;
2
use serde_json::json;
3
4
use super::{
5
    Cause, Choice, Floor, Item, Notice, Outcome, Run, Story,
6
    balance::{
7
        CAUGHT_MELONED, COCONUT_ODDS, ENERGY_ODDS, KETTLE_ODDS, KITCHEN_ICE_ODDS, KITCHEN_TOLD,
8
        MELON_SHAME, RAID_NOISE, RUPTURE_LINE, WATERMELON,
9
    },
10
};
11
use crate::problem::Problem;
12
13
#[derive(Clone, Copy)]
14
enum Loot {
15
    CoconutOil,
16
    EnergyDrink,
17
    IcePack,
18
    Kettle,
19
    Nothing,
20
}
21
22
impl Loot {
23
    const ALL: [Self; 5] = [
24
        Self::CoconutOil,
25
        Self::EnergyDrink,
26
        Self::IcePack,
27
        Self::Kettle,
28
        Self::Nothing,
29
    ];
30
31
    /// 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.
32
    fn weight(self) -> u32 {
33
        match self {
34
            Self::CoconutOil => 120 / COCONUT_ODDS,
35
            Self::EnergyDrink => 120 / ENERGY_ODDS,
36
            Self::IcePack => 120 / KITCHEN_ICE_ODDS,
37
            Self::Kettle => 120 / KETTLE_ODDS,
38
            Self::Nothing => 120 - Self::ALL[..4].iter().map(|l| l.weight()).sum::<u32>(),
39
        }
40
    }
41
42
    fn sight(self) -> &'static str {
43
        match self {
44
            Self::CoconutOil => "A jar of coconut oil, half of it already gone.",
45
            Self::EnergyDrink => "An energy drink, cold and faintly alarming.",
46
            Self::IcePack => "An ice pack, buried under the peas.",
47
            Self::Kettle => "The kettle has just boiled.",
48
            Self::Nothing => "Nothing. The fridge is ordinary.",
49
        }
50
    }
51
}
52
53
impl Run {
54
    /// The household keeps count, so every raid makes noise and the noise makes the next telling likelier.
55
    pub fn raid_kitchen(&mut self) -> Result<Outcome, Problem> {
56
        if !self.floor.has_kitchen() {
57
            return Err(Problem::new(
58
                StatusCode::NOT_FOUND,
59
                "no-kitchen",
60
                "There is no kitchen on this floor.",
61
            ));
62
        }
63
        if self.here.kitchen.closed {
64
            return Err(self.off_limits());
65
        }
66
        if self.online.story == Story::Pending {
67
            return Ok(self.branch_offer());
68
        }
69
        self.begin_turn()?;
70
        if self.chance(
71
            self.decision("rupture", "Does this raid of the fridge rupture something?"),
72
            self.rupture_odds(),
73
        ) {
74
            return Err(self.rupture());
75
        }
76
        self.make_noise(RAID_NOISE);
77
        let loot = Loot::ALL[self.oracle.pick(
78
            self.decision("loot", "What does the fridge give up on this raid?"),
79
            &Loot::ALL.map(Loot::weight),
80
            &Loot::ALL.map(Loot::sight),
81
        )];
82
        let melon = self.body.eq > RUPTURE_LINE
83
            && self.oracle.ratio(
84
                self.decision(
85
                    "watermelon",
86
                    "Is there a watermelon in the fridge, exactly the right shape?",
87
                ),
88
                WATERMELON.0,
89
                WATERMELON.1,
90
            );
91
        self.settle();
92
        if self.oracle.ratio(
93
            self.decision(
94
                "kitchen-told",
95
                "Has the household had enough of the raiding?",
96
            ),
97
            u32::from(self.standing.suspicion) + 1,
98
            KITCHEN_TOLD,
99
        ) {
100
            self.here.kitchen.closed = true;
101
            self.end_kitchen_visit();
102
            self.notices.push(if self.floor == Floor::Nans {
103
                Notice::StopRaidingTheFridge
104
            } else {
105
                Notice::StopRaidingTheKitchen
106
            });
107
            return Err(self.off_limits());
108
        }
109
        if melon {
110
            self.here.kitchen.melon_offered = true;
111
            return Ok(self.melon_offer());
112
        }
113
        Ok(self.unpack(loot))
114
    }
115
116
    fn off_limits(&self) -> Problem {
117
        Problem::new(
118
            StatusCode::GONE,
119
            "kitchen-off-limits",
120
            "The kitchen is off limits.",
121
        )
122
    }
123
124
    fn unpack(&mut self, loot: Loot) -> Outcome {
125
        let item = match loot {
126
            Loot::CoconutOil => Item::CoconutOil,
127
            Loot::EnergyDrink => Item::EnergyDrink,
128
            Loot::IcePack => Item::IcePack,
129
            Loot::Kettle => {
130
                self.here.kitchen.kettle_boiled = true;
131
                return Outcome::json(
132
                    StatusCode::OK,
133
                    json!({ "detail": format!("{} The towel can be warmed once more.", loot.sight()) }),
134
                );
135
            }
136
            Loot::Nothing => return Outcome::empty(StatusCode::NO_CONTENT),
137
        };
138
        self.items.grant(item);
139
        Outcome::json(StatusCode::OK, json!({ "detail": loot.sight() }))
140
    }
141
142
    fn melon_offer(&self) -> Outcome {
143
        Outcome::choices(
144
            "The fridge holds a watermelon. It is exactly the right shape.",
145
            &[
146
                Choice {
147
                    href: self.path("/kitchen/watermelon"),
148
                    method: "POST",
149
                    title: "Commit to the watermelon",
150
                },
151
                Choice {
152
                    href: self.path(""),
153
                    method: "GET",
154
                    title: "Close the fridge",
155
                },
156
            ],
157
        )
158
    }
159
160
    /// A bail-out that costs no turn. The more the household has already heard, the likelier Mum is in the doorway.
161
    pub fn commit_melon(&mut self) -> Result<Outcome, Problem> {
162
        if !self.here.kitchen.melon_offered {
163
            return Err(Problem::new(
164
                StatusCode::CONFLICT,
165
                "no-melon-on-offer",
166
                "There is no watermelon on offer.",
167
            ));
168
        }
169
        self.here.kitchen.melon_offered = false;
170
        self.reset_eq(0);
171
        self.standing.shame += MELON_SHAME;
172
        self.touch();
173
        if self.oracle.ratio(
174
            self.decision(
175
                "caught-meloned",
176
                "Is Mum standing in the doorway as you look up?",
177
            ),
178
            u32::from(self.standing.suspicion) + 2,
179
            CAUGHT_MELONED,
180
        ) {
181
            return Err(self.end(Cause::Meloned));
182
        }
183
        Ok(Outcome::json(
184
            StatusCode::OK,
185
            json!({
186
                "detail": "The watermelon did not survive, and neither did the mood. EQ is gone, the gains are kept, and the shame is considerable.",
187
            }),
188
        ))
189
    }
190
}