atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
624fe9c09c8eefdb9679b36259b76f6439318edc
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/game/dispatch.rsRust211 lines6.9 KB
1
use super::{
2
    Floor, Lube, Outcome, Run, Story, Technique,
3
    techniques::{Training, Utility},
4
};
5
use crate::problem::Problem;
6
use axum::http::{StatusCode, header};
7
use serde_json::{Value, json};
8
use std::{fmt::Write as _, ops::ControlFlow};
9
10
impl Run {
11
    pub fn jelq(
12
        &mut self,
13
        techniques: &[Technique],
14
        intensity: i32,
15
        lube: Option<Lube>,
16
    ) -> Result<Outcome, Problem> {
17
        if techniques != [Technique::Utility(Utility::Energy)] {
18
            self.check_exhausted()?;
19
        }
20
        if self.online.story == Story::Pending {
21
            return Ok(self.branch_offer());
22
        }
23
        if self.floor == Floor::Forum {
24
            return Ok(Outcome::json(
25
                StatusCode::SEE_OTHER,
26
                json!({ "detail": "Refer to the forum." }),
27
            )
28
            .header(header::LOCATION, self.path("/thread")));
29
        }
30
        self.check_cage()?;
31
        match *techniques {
32
            [Technique::Utility(utility)] => self.utility(utility, lube),
33
            [Technique::Training(training)] => self.single(training, intensity, lube),
34
            [Technique::Training(a), Technique::Training(b)] => self.combo(a, b, intensity, lube),
35
            [_, _] => Err(Problem::new(
36
                StatusCode::BAD_REQUEST,
37
                "combo-invalid",
38
                "Only training techniques can be combined.",
39
            )),
40
            _ => Err(Problem::new(
41
                StatusCode::BAD_REQUEST,
42
                "too-many-techniques",
43
                "Two techniques at most. The server has limits even if you do not.",
44
            )),
45
        }
46
    }
47
48
    pub(super) fn utility(
49
        &mut self,
50
        utility: Utility,
51
        lube: Option<Lube>,
52
    ) -> Result<Outcome, Problem> {
53
        match utility {
54
            Utility::Warmup => return self.warm_up(),
55
            Utility::Ice => return self.ice(),
56
            Utility::Supplement => return self.supplement(),
57
            Utility::Meditate => return self.meditate(),
58
            Utility::Energy => return self.energy(),
59
            Utility::Edge | Utility::Pump => {}
60
        }
61
        let lube = lube.filter(|_| utility == Utility::Edge);
62
        self.ready(Technique::Utility(utility), lube)?;
63
        self.check_storage()?;
64
        self.lube_available(lube)?;
65
        self.check_clock()?;
66
        self.floor_enemy()?;
67
        match utility {
68
            Utility::Edge => self.edge(lube),
69
            _ => self.pump(),
70
        }
71
    }
72
73
    pub(super) fn single(
74
        &mut self,
75
        training: Training,
76
        intensity: i32,
77
        lube: Option<Lube>,
78
    ) -> Result<Outcome, Problem> {
79
        self.ready(Technique::Training(training), lube)?;
80
        self.check_storage()?;
81
        self.lube_available(lube)?;
82
        self.check_clock()?;
83
        self.floor_enemy()?;
84
        self.take_lube(lube)?;
85
        let cold = self.cold();
86
        self.chafe(lube, intensity, cold);
87
        self.commit(intensity, lube, 1)?;
88
        if let ControlFlow::Break(interrupted) = self.mishaps(training, intensity, cold)? {
89
            return Ok(interrupted);
90
        }
91
        if let ControlFlow::Break(interrupted) = self.hazards(intensity)? {
92
            return Ok(interrupted);
93
        }
94
        let outcome = self.roll(training, intensity, lube)?;
95
        Ok(self.gossip(outcome))
96
    }
97
98
    pub(super) fn combo(
99
        &mut self,
100
        a: Training,
101
        b: Training,
102
        intensity: i32,
103
        lube: Option<Lube>,
104
    ) -> Result<Outcome, Problem> {
105
        if a == b {
106
            return Err(Problem::new(
107
                StatusCode::BAD_REQUEST,
108
                "combo-invalid",
109
                "A technique cannot be combined with itself. The server has checked.",
110
            ));
111
        }
112
        self.ready(Technique::Training(a), lube)?;
113
        self.ready(Technique::Training(b), lube)?;
114
        self.check_storage()?;
115
        self.lube_available(lube)?;
116
        self.check_clock()?;
117
        self.floor_enemy()?;
118
        self.take_lube(lube)?;
119
        let cold = self.cold();
120
        self.chafe(lube, intensity, cold);
121
        self.chafe(lube, intensity, cold);
122
        self.commit(intensity, lube, 2)?;
123
        for training in [a, b] {
124
            if let ControlFlow::Break(interrupted) = self.mishaps(training, intensity, cold)? {
125
                return Ok(interrupted);
126
            }
127
        }
128
        if let ControlFlow::Break(interrupted) = self.hazards(intensity)? {
129
            return Ok(interrupted);
130
        }
131
132
        let mut reports = Vec::new();
133
        for training in [a, b] {
134
            let result = self.roll(training, intensity, lube);
135
            if self.ended.is_some() {
136
                return result;
137
            }
138
            reports.push((
139
                training,
140
                Report::of(result.map(|outcome| self.gossip(outcome))),
141
            ));
142
        }
143
144
        let mut xml = String::from(
145
            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<D:multistatus xmlns:D=\"DAV:\">\n",
146
        );
147
        for (training, report) in reports {
148
            let _ = write!(
149
                xml,
150
                "  <D:response>\n    <D:href>{}?technique={}</D:href>\n    <D:status>HTTP/1.1 {} {}</D:status>\n",
151
                self.path(""),
152
                training.name(),
153
                report.status.as_u16(),
154
                crate::problem::reason(report.status),
155
            );
156
            if let Some(location) = report.location {
157
                let _ = writeln!(
158
                    xml,
159
                    "    <D:location><D:href>{}</D:href></D:location>",
160
                    escape(&location)
161
                );
162
            }
163
            if let Some(text) = report.text {
164
                let _ = writeln!(
165
                    xml,
166
                    "    <D:responsedescription>{}</D:responsedescription>",
167
                    escape(&text)
168
                );
169
            }
170
            xml.push_str("  </D:response>\n");
171
        }
172
        xml.push_str(
173
            "  <D:responsedescription>Each technique has been reported separately.</D:responsedescription>\n</D:multistatus>\n",
174
        );
175
        Ok(Outcome::raw(
176
            StatusCode::MULTI_STATUS,
177
            "application/xml; charset=utf-8",
178
            xml,
179
        ))
180
    }
181
}
182
183
/// What one technique of a combo would have answered on its own.
184
struct Report {
185
    status: StatusCode,
186
    location: Option<String>,
187
    text: Option<String>,
188
}
189
190
impl Report {
191
    fn of(result: Result<Outcome, Problem>) -> Self {
192
        match result {
193
            Ok(outcome) => Self {
194
                status: outcome.status(),
195
                location: outcome.header_value(&header::LOCATION).map(str::to_owned),
196
                text: outcome.body().map(Value::to_string),
197
            },
198
            Err(problem) => Self {
199
                status: problem.status(),
200
                location: None,
201
                text: Some(Value::Object(problem.document()).to_string()),
202
            },
203
        }
204
    }
205
}
206
207
fn escape(text: &str) -> String {
208
    text.replace('&', "&amp;")
209
        .replace('<', "&lt;")
210
        .replace('>', "&gt;")
211
}