atagen

jelq

public

an http based roguelike rpg about jelqing

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