atagen

jelq

public

an http based roguelike rpg about jelqing

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