atagen

jelq

public

an http based roguelike rpg about jelqing

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