atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
53bce0d930007b21fb1f130865979bbd959edcc5
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/game/travel.rsRust190 lines6.3 KB
1
use super::{
2
    Cause, Elder, Floor, FloorState, Notice, Outcome, Run,
3
    balance::{
4
        EQ_BAND, FLOOR_ALLOWANCE, FLOOR_LIMIT, FLOOR_TURNS, FRICTION_FLOOR, LOCKER_ROOM_BAR,
5
        LOCKER_ROOM_OTHERS, LOCKER_ROOM_SHAME, OVERSTAY_BUSTED, OVERSTAY_EQ, OVERSTAY_SHAME,
6
        STARTING_EQ, ZIP,
7
    },
8
    measure::round2,
9
};
10
use crate::problem::Problem;
11
use axum::http::{StatusCode, header};
12
use serde_json::{Value, json};
13
14
/// How a floor was left. Only a proper exit is paid.
15
#[derive(Clone, Copy, PartialEq, Eq)]
16
pub(super) enum Exit {
17
    Proper,
18
    Premature,
19
    Overstayed,
20
}
21
22
impl Run {
23
    /// Whether enough turns have passed for a timed floor to let the player go.
24
    pub(super) fn served_floor(&self) -> bool {
25
        self.here.turns >= FLOOR_TURNS
26
    }
27
28
    pub(super) fn next_floor(&mut self, exit: Exit) {
29
        let Some(next) = self.floor.next() else {
30
            return;
31
        };
32
        let warm = EQ_BAND.contains(&self.body.eq);
33
        self.floor = next;
34
        self.here = FloorState {
35
            warm_arrival: warm,
36
            ..FloorState::default()
37
        };
38
        self.realise(true);
39
        self.reset_eq(STARTING_EQ);
40
        self.standing.suspicion = 0;
41
        self.shed(FRICTION_FLOOR);
42
        self.household.mum = None;
43
        if self.floor == Floor::ChangingRoom && !self.holds_own_in_the_locker_room() {
44
            self.standing.shame += LOCKER_ROOM_SHAME;
45
            self.notices.push(Notice::KevIsTheBar);
46
        }
47
        if exit == Exit::Proper {
48
            self.standing.cash += FLOOR_ALLOWANCE;
49
        }
50
        self.touch();
51
    }
52
53
    pub(super) fn check_clock(&mut self) -> Result<(), Problem> {
54
        if self.floor.timed() && self.here.turns + 1 >= FLOOR_LIMIT {
55
            return Err(self.overstay());
56
        }
57
        Ok(())
58
    }
59
60
    /// A floor past its limit either ends the run or moves you along, shamed and deflated.
61
    fn overstay(&mut self) -> Problem {
62
        self.touch();
63
        if self.chance(
64
            self.decision(
65
                "overstayed",
66
                "The floor has run long. Does somebody walk in?",
67
            ),
68
            OVERSTAY_BUSTED,
69
        ) {
70
            return self.end(Cause::Overstayed);
71
        }
72
        self.standing.shame += OVERSTAY_SHAME;
73
        self.next_floor(Exit::Overstayed);
74
        self.here.warm_arrival = false;
75
        self.reset_eq(OVERSTAY_EQ);
76
        Problem::new(
77
            StatusCode::REQUEST_TIMEOUT,
78
            "floor-timed-out",
79
            "The floor has timed out. You have been moved along.",
80
        )
81
        .header(header::CONNECTION, "close")
82
    }
83
84
    pub fn advance(&mut self) -> Result<Outcome, Problem> {
85
        match self.floor {
86
            Floor::Clinic => {
87
                return Err(Problem::new(
88
                    StatusCode::CONFLICT,
89
                    "no-further-floor",
90
                    "There is no further floor. There is only the diagnosis.",
91
                ));
92
            }
93
            Floor::Forum if self.online.elder != Elder::Beaten => {
94
                return Err(Problem::new(
95
                    StatusCode::CONFLICT,
96
                    "elder-unaddressed",
97
                    "The Elder has not been addressed.",
98
                )
99
                .with("elder", self.path("/elder")));
100
            }
101
            floor if floor.timed() && !self.served_floor() => {
102
                return Err(Problem::new(
103
                    StatusCode::TOO_EARLY,
104
                    "floor-not-over",
105
                    format!(
106
                        "The floor is not over. {} turns remain.",
107
                        FLOOR_TURNS - self.here.turns
108
                    ),
109
                ));
110
            }
111
            _ => {}
112
        }
113
        if self.chance(
114
            self.decision(
115
                "zip",
116
                "As the floor ends and you put yourself away, does the zip catch?",
117
            ),
118
            ZIP,
119
        ) {
120
            return Err(self.end(Cause::Zip));
121
        }
122
        let left = self.floor.name();
123
        let reverted = self.here.temp_length;
124
        self.next_floor(Exit::Proper);
125
        let warm = if self.here.warm_arrival {
126
            ", and the warmth came with you"
127
        } else {
128
            ""
129
        };
130
        let mut body = json!({
131
            "detail": format!("You leave {left}. You have arrived at {}{warm}.", self.floor.name()),
132
            "note": self.floor.note(),
133
        });
134
        if reverted > 0.0 {
135
            body["reverted"] = json!(round2(reverted));
136
        }
137
        Ok(
138
            Outcome::json(StatusCode::SEE_OTHER, body)
139
                .header(header::LOCATION, self.path("/floor")),
140
        )
141
    }
142
143
    pub fn floor_view(&self) -> Value {
144
        let mut view = json!({
145
            "name": self.floor.name(),
146
            "note": self.floor.note(),
147
            "turns": self.here.turns,
148
            "turns_required": self.floor.timed().then_some(FLOOR_TURNS),
149
            "exit": { "href": self.path("/floor"), "method": "POST" },
150
        });
151
        match self.floor {
152
            Floor::ChangingRoom => {
153
                let others: Vec<Value> = LOCKER_ROOM_OTHERS
154
                    .into_iter()
155
                    .chain([("Kev", LOCKER_ROOM_BAR)])
156
                    .map(|(name, length)| json!({ "name": name, "self_reported_length": length }))
157
                    .collect();
158
                view["others"] = json!(others);
159
            }
160
            Floor::Hotel => view["portal"] = json!(self.path("/portal")),
161
            Floor::Forum => {
162
                view["thread"] = json!(self.path("/thread"));
163
                view["elder"] = json!(self.path("/elder"));
164
            }
165
            Floor::Clinic => view["diagnosis"] = json!(self.path("/diagnosis")),
166
            _ => {}
167
        }
168
        if self.floor.shop_open() {
169
            view["shop"] = json!(self.path("/shop"));
170
        }
171
        view
172
    }
173
174
    pub fn accept_terms(&mut self) -> Result<Outcome, Problem> {
175
        if self.floor != Floor::Hotel {
176
            return Err(Problem::new(
177
                StatusCode::NOT_FOUND,
178
                "no-portal",
179
                "There is no captive portal here. There is only the regular kind of captivity.",
180
            ));
181
        }
182
        self.here.wifi_accepted = true;
183
        self.touch();
184
        Ok(Outcome::json(
185
            StatusCode::SEE_OTHER,
186
            json!({ "detail": "Connected. The hotel does not want to know." }),
187
        )
188
        .header(header::LOCATION, self.path("")))
189
    }
190
}