atagen

jelq

public

an http based roguelike rpg about jelqing

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

Blame: src/game/travel.rs

782069860236atagen2026-10-10 12:44 UTC1
use super::{
8b8e83329a1datagen2026-10-12 12:01 UTC2
    Cause, Elder, Floor, FloorState, Notice, Outcome, Run,
0d604ee39751atagen2026-10-10 14:15 UTC3
    balance::{
6033b0db186catagen2026-10-13 12:44 UTC4
        EQ_BAND, FLOOR_ALLOWANCE, FLOOR_LIMIT, FLOOR_TURNS, FRICTION_FLOOR, LOCKER_ROOM_BAR,
6033b0db186catagen2026-10-13 12:44 UTC5
        LOCKER_ROOM_OTHERS, LOCKER_ROOM_SHAME, OVERSTAY_BUSTED, OVERSTAY_EQ, OVERSTAY_SHAME,
6033b0db186catagen2026-10-13 12:44 UTC6
        STARTING_EQ, ZIP,
0d604ee39751atagen2026-10-10 14:15 UTC7
    },
8b8e83329a1datagen2026-10-12 12:01 UTC8
    measure::round2,
782069860236atagen2026-10-10 12:44 UTC9
};
782069860236atagen2026-10-10 12:44 UTC10
use crate::problem::Problem;
8b8e83329a1datagen2026-10-12 12:01 UTC11
use axum::http::{StatusCode, header};
8b8e83329a1datagen2026-10-12 12:01 UTC12
use serde_json::{Value, json};
8b8e83329a1datagen2026-10-12 12:01 UTC13
8b8e83329a1datagen2026-10-12 12:01 UTC14
/// How a floor was left. Only a proper exit is paid.
8b8e83329a1datagen2026-10-12 12:01 UTC15
#[derive(Clone, Copy, PartialEq, Eq)]
8b8e83329a1datagen2026-10-12 12:01 UTC16
pub(super) enum Exit {
8b8e83329a1datagen2026-10-12 12:01 UTC17
    Proper,
8b8e83329a1datagen2026-10-12 12:01 UTC18
    Premature,
3ac624339c98atagen2026-10-13 12:21 UTC19
    Overstayed,
8b8e83329a1datagen2026-10-12 12:01 UTC20
}
782069860236atagen2026-10-10 12:44 UTC21
782069860236atagen2026-10-10 12:44 UTC22
impl Run {
624fe9c09c8eatagen2026-10-14 12:11 UTC23
    /// Whether enough turns have passed for a timed floor to let the player go.
624fe9c09c8eatagen2026-10-14 12:11 UTC24
    pub(super) fn served_floor(&self) -> bool {
624fe9c09c8eatagen2026-10-14 12:11 UTC25
        self.here.turns >= FLOOR_TURNS
624fe9c09c8eatagen2026-10-14 12:11 UTC26
    }
624fe9c09c8eatagen2026-10-14 12:11 UTC27
8b8e83329a1datagen2026-10-12 12:01 UTC28
    pub(super) fn next_floor(&mut self, exit: Exit) {
782069860236atagen2026-10-10 12:44 UTC29
        let Some(next) = self.floor.next() else {
782069860236atagen2026-10-10 12:44 UTC30
            return;
782069860236atagen2026-10-10 12:44 UTC31
        };
8b8e83329a1datagen2026-10-12 12:01 UTC32
        let warm = EQ_BAND.contains(&self.body.eq);
782069860236atagen2026-10-10 12:44 UTC33
        self.floor = next;
4347977b8192atagen2026-10-11 04:56 UTC34
        self.here = FloorState {
4347977b8192atagen2026-10-11 04:56 UTC35
            warm_arrival: warm,
4347977b8192atagen2026-10-11 04:56 UTC36
            ..FloorState::default()
4347977b8192atagen2026-10-11 04:56 UTC37
        };
6033b0db186catagen2026-10-13 12:44 UTC38
        self.realise(true);
14de3e1e8dc2atagen2026-10-11 06:19 UTC39
        self.reset_eq(STARTING_EQ);
8b8e83329a1datagen2026-10-12 12:01 UTC40
        self.standing.suspicion = 0;
5dfebcc0ac43atagen2026-10-11 05:28 UTC41
        self.shed(FRICTION_FLOOR);
8b8e83329a1datagen2026-10-12 12:01 UTC42
        self.household.mum = None;
058650ec79d6atagen2026-10-12 10:49 UTC43
        if self.floor == Floor::ChangingRoom && !self.holds_own_in_the_locker_room() {
8b8e83329a1datagen2026-10-12 12:01 UTC44
            self.standing.shame += LOCKER_ROOM_SHAME;
8b8e83329a1datagen2026-10-12 12:01 UTC45
            self.notices.push(Notice::KevIsTheBar);
058650ec79d6atagen2026-10-12 10:49 UTC46
        }
8b8e83329a1datagen2026-10-12 12:01 UTC47
        if exit == Exit::Proper {
8b8e83329a1datagen2026-10-12 12:01 UTC48
            self.standing.cash += FLOOR_ALLOWANCE;
782069860236atagen2026-10-10 12:44 UTC49
        }
782069860236atagen2026-10-10 12:44 UTC50
        self.touch();
782069860236atagen2026-10-10 12:44 UTC51
    }
782069860236atagen2026-10-10 12:44 UTC52
6033b0db186catagen2026-10-13 12:44 UTC53
    pub(super) fn check_clock(&mut self) -> Result<(), Problem> {
6033b0db186catagen2026-10-13 12:44 UTC54
        if self.floor.timed() && self.here.turns + 1 >= FLOOR_LIMIT {
6033b0db186catagen2026-10-13 12:44 UTC55
            return Err(self.overstay());
6033b0db186catagen2026-10-13 12:44 UTC56
        }
6033b0db186catagen2026-10-13 12:44 UTC57
        Ok(())
6033b0db186catagen2026-10-13 12:44 UTC58
    }
6033b0db186catagen2026-10-13 12:44 UTC59
3ac624339c98atagen2026-10-13 12:21 UTC60
    /// A floor past its limit either ends the run or moves you along, shamed and deflated.
6033b0db186catagen2026-10-13 12:44 UTC61
    fn overstay(&mut self) -> Problem {
3ac624339c98atagen2026-10-13 12:21 UTC62
        self.touch();
3ac624339c98atagen2026-10-13 12:21 UTC63
        if self.chance(
3ac624339c98atagen2026-10-13 12:21 UTC64
            self.decision(
3ac624339c98atagen2026-10-13 12:21 UTC65
                "overstayed",
3ac624339c98atagen2026-10-13 12:21 UTC66
                "The floor has run long. Does somebody walk in?",
3ac624339c98atagen2026-10-13 12:21 UTC67
            ),
3ac624339c98atagen2026-10-13 12:21 UTC68
            OVERSTAY_BUSTED,
3ac624339c98atagen2026-10-13 12:21 UTC69
        ) {
3ac624339c98atagen2026-10-13 12:21 UTC70
            return self.end(Cause::Overstayed);
3ac624339c98atagen2026-10-13 12:21 UTC71
        }
3ac624339c98atagen2026-10-13 12:21 UTC72
        self.standing.shame += OVERSTAY_SHAME;
3ac624339c98atagen2026-10-13 12:21 UTC73
        self.next_floor(Exit::Overstayed);
3ac624339c98atagen2026-10-13 12:21 UTC74
        self.here.warm_arrival = false;
3ac624339c98atagen2026-10-13 12:21 UTC75
        self.reset_eq(OVERSTAY_EQ);
3ac624339c98atagen2026-10-13 12:21 UTC76
        Problem::new(
3ac624339c98atagen2026-10-13 12:21 UTC77
            StatusCode::REQUEST_TIMEOUT,
3ac624339c98atagen2026-10-13 12:21 UTC78
            "floor-timed-out",
3ac624339c98atagen2026-10-13 12:21 UTC79
            "The floor has timed out. You have been moved along.",
3ac624339c98atagen2026-10-13 12:21 UTC80
        )
3ac624339c98atagen2026-10-13 12:21 UTC81
        .header(header::CONNECTION, "close")
3ac624339c98atagen2026-10-13 12:21 UTC82
    }
3ac624339c98atagen2026-10-13 12:21 UTC83
782069860236atagen2026-10-10 12:44 UTC84
    pub fn advance(&mut self) -> Result<Outcome, Problem> {
782069860236atagen2026-10-10 12:44 UTC85
        match self.floor {
782069860236atagen2026-10-10 12:44 UTC86
            Floor::Clinic => {
782069860236atagen2026-10-10 12:44 UTC87
                return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC88
                    StatusCode::CONFLICT,
782069860236atagen2026-10-10 12:44 UTC89
                    "no-further-floor",
782069860236atagen2026-10-10 12:44 UTC90
                    "There is no further floor. There is only the diagnosis.",
782069860236atagen2026-10-10 12:44 UTC91
                ));
782069860236atagen2026-10-10 12:44 UTC92
            }
8b8e83329a1datagen2026-10-12 12:01 UTC93
            Floor::Forum if self.online.elder != Elder::Beaten => {
782069860236atagen2026-10-10 12:44 UTC94
                return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC95
                    StatusCode::CONFLICT,
782069860236atagen2026-10-10 12:44 UTC96
                    "elder-unaddressed",
782069860236atagen2026-10-10 12:44 UTC97
                    "The Elder has not been addressed.",
782069860236atagen2026-10-10 12:44 UTC98
                )
782069860236atagen2026-10-10 12:44 UTC99
                .with("elder", self.path("/elder")));
782069860236atagen2026-10-10 12:44 UTC100
            }
624fe9c09c8eatagen2026-10-14 12:11 UTC101
            floor if floor.timed() && !self.served_floor() => {
782069860236atagen2026-10-10 12:44 UTC102
                return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC103
                    StatusCode::TOO_EARLY,
782069860236atagen2026-10-10 12:44 UTC104
                    "floor-not-over",
782069860236atagen2026-10-10 12:44 UTC105
                    format!(
782069860236atagen2026-10-10 12:44 UTC106
                        "The floor is not over. {} turns remain.",
782069860236atagen2026-10-10 12:44 UTC107
                        FLOOR_TURNS - self.here.turns
782069860236atagen2026-10-10 12:44 UTC108
                    ),
782069860236atagen2026-10-10 12:44 UTC109
                ));
782069860236atagen2026-10-10 12:44 UTC110
            }
782069860236atagen2026-10-10 12:44 UTC111
            _ => {}
782069860236atagen2026-10-10 12:44 UTC112
        }
3f9091557ec3atagen2026-10-11 11:43 UTC113
        if self.chance(
3f9091557ec3atagen2026-10-11 11:43 UTC114
            self.decision(
3f9091557ec3atagen2026-10-11 11:43 UTC115
                "zip",
3f9091557ec3atagen2026-10-11 11:43 UTC116
                "As the floor ends and you put yourself away, does the zip catch?",
3f9091557ec3atagen2026-10-11 11:43 UTC117
            ),
3f9091557ec3atagen2026-10-11 11:43 UTC118
            ZIP,
3f9091557ec3atagen2026-10-11 11:43 UTC119
        ) {
9683736a2c5eatagen2026-10-10 13:15 UTC120
            return Err(self.end(Cause::Zip));
782069860236atagen2026-10-10 12:44 UTC121
        }
7d3fb63208bfatagen2026-10-11 04:38 UTC122
        let left = self.floor.name();
782069860236atagen2026-10-10 12:44 UTC123
        let reverted = self.here.temp_length;
8b8e83329a1datagen2026-10-12 12:01 UTC124
        self.next_floor(Exit::Proper);
4347977b8192atagen2026-10-11 04:56 UTC125
        let warm = if self.here.warm_arrival {
4347977b8192atagen2026-10-11 04:56 UTC126
            ", and the warmth came with you"
4347977b8192atagen2026-10-11 04:56 UTC127
        } else {
4347977b8192atagen2026-10-11 04:56 UTC128
            ""
4347977b8192atagen2026-10-11 04:56 UTC129
        };
782069860236atagen2026-10-10 12:44 UTC130
        let mut body = json!({
4347977b8192atagen2026-10-11 04:56 UTC131
            "detail": format!("You leave {left}. You have arrived at {}{warm}.", self.floor.name()),
782069860236atagen2026-10-10 12:44 UTC132
            "note": self.floor.note(),
782069860236atagen2026-10-10 12:44 UTC133
        });
782069860236atagen2026-10-10 12:44 UTC134
        if reverted > 0.0 {
782069860236atagen2026-10-10 12:44 UTC135
            body["reverted"] = json!(round2(reverted));
782069860236atagen2026-10-10 12:44 UTC136
        }
782069860236atagen2026-10-10 12:44 UTC137
        Ok(
782069860236atagen2026-10-10 12:44 UTC138
            Outcome::json(StatusCode::SEE_OTHER, body)
782069860236atagen2026-10-10 12:44 UTC139
                .header(header::LOCATION, self.path("/floor")),
782069860236atagen2026-10-10 12:44 UTC140
        )
782069860236atagen2026-10-10 12:44 UTC141
    }
782069860236atagen2026-10-10 12:44 UTC142
782069860236atagen2026-10-10 12:44 UTC143
    pub fn floor_view(&self) -> Value {
782069860236atagen2026-10-10 12:44 UTC144
        let mut view = json!({
782069860236atagen2026-10-10 12:44 UTC145
            "name": self.floor.name(),
782069860236atagen2026-10-10 12:44 UTC146
            "note": self.floor.note(),
782069860236atagen2026-10-10 12:44 UTC147
            "turns": self.here.turns,
782069860236atagen2026-10-10 12:44 UTC148
            "turns_required": self.floor.timed().then_some(FLOOR_TURNS),
782069860236atagen2026-10-10 12:44 UTC149
            "exit": { "href": self.path("/floor"), "method": "POST" },
782069860236atagen2026-10-10 12:44 UTC150
        });
782069860236atagen2026-10-10 12:44 UTC151
        match self.floor {
9683736a2c5eatagen2026-10-10 13:15 UTC152
            Floor::ChangingRoom => {
8b8e83329a1datagen2026-10-12 12:01 UTC153
                let others: Vec<Value> = LOCKER_ROOM_OTHERS
8b8e83329a1datagen2026-10-12 12:01 UTC154
                    .into_iter()
8b8e83329a1datagen2026-10-12 12:01 UTC155
                    .chain([("Kev", LOCKER_ROOM_BAR)])
8b8e83329a1datagen2026-10-12 12:01 UTC156
                    .map(|(name, length)| json!({ "name": name, "self_reported_length": length }))
8b8e83329a1datagen2026-10-12 12:01 UTC157
                    .collect();
8b8e83329a1datagen2026-10-12 12:01 UTC158
                view["others"] = json!(others);
782069860236atagen2026-10-10 12:44 UTC159
            }
782069860236atagen2026-10-10 12:44 UTC160
            Floor::Hotel => view["portal"] = json!(self.path("/portal")),
782069860236atagen2026-10-10 12:44 UTC161
            Floor::Forum => {
a11497af08aaatagen2026-10-13 12:04 UTC162
                view["thread"] = json!(self.path("/thread"));
782069860236atagen2026-10-10 12:44 UTC163
                view["elder"] = json!(self.path("/elder"));
782069860236atagen2026-10-10 12:44 UTC164
            }
782069860236atagen2026-10-10 12:44 UTC165
            Floor::Clinic => view["diagnosis"] = json!(self.path("/diagnosis")),
782069860236atagen2026-10-10 12:44 UTC166
            _ => {}
782069860236atagen2026-10-10 12:44 UTC167
        }
782069860236atagen2026-10-10 12:44 UTC168
        if self.floor.shop_open() {
782069860236atagen2026-10-10 12:44 UTC169
            view["shop"] = json!(self.path("/shop"));
782069860236atagen2026-10-10 12:44 UTC170
        }
782069860236atagen2026-10-10 12:44 UTC171
        view
782069860236atagen2026-10-10 12:44 UTC172
    }
782069860236atagen2026-10-10 12:44 UTC173
782069860236atagen2026-10-10 12:44 UTC174
    pub fn accept_terms(&mut self) -> Result<Outcome, Problem> {
782069860236atagen2026-10-10 12:44 UTC175
        if self.floor != Floor::Hotel {
782069860236atagen2026-10-10 12:44 UTC176
            return Err(Problem::new(
782069860236atagen2026-10-10 12:44 UTC177
                StatusCode::NOT_FOUND,
782069860236atagen2026-10-10 12:44 UTC178
                "no-portal",
782069860236atagen2026-10-10 12:44 UTC179
                "There is no captive portal here. There is only the regular kind of captivity.",
782069860236atagen2026-10-10 12:44 UTC180
            ));
782069860236atagen2026-10-10 12:44 UTC181
        }
782069860236atagen2026-10-10 12:44 UTC182
        self.here.wifi_accepted = true;
782069860236atagen2026-10-10 12:44 UTC183
        self.touch();
782069860236atagen2026-10-10 12:44 UTC184
        Ok(Outcome::json(
782069860236atagen2026-10-10 12:44 UTC185
            StatusCode::SEE_OTHER,
782069860236atagen2026-10-10 12:44 UTC186
            json!({ "detail": "Connected. The hotel does not want to know." }),
782069860236atagen2026-10-10 12:44 UTC187
        )
782069860236atagen2026-10-10 12:44 UTC188
        .header(header::LOCATION, self.path("")))
782069860236atagen2026-10-10 12:44 UTC189
    }
782069860236atagen2026-10-10 12:44 UTC190
}