atagen

jelq

public

an http based roguelike rpg about jelqing

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

Blame: src/game/dispatch.rs

8b8e83329a1datagen2026-10-12 12:01 UTC1
use super::{
8b8e83329a1datagen2026-10-12 12:01 UTC2
    Floor, Lube, Outcome, Run, Story, Technique,
8b8e83329a1datagen2026-10-12 12:01 UTC3
    techniques::{Training, Utility},
8b8e83329a1datagen2026-10-12 12:01 UTC4
};
8b8e83329a1datagen2026-10-12 12:01 UTC5
use crate::problem::Problem;
8b8e83329a1datagen2026-10-12 12:01 UTC6
use axum::http::{StatusCode, header};
98432b6057aaatagen2026-10-14 09:03 UTC7
use serde_json::{Value, json};
8b8e83329a1datagen2026-10-12 12:01 UTC8
use std::{fmt::Write as _, ops::ControlFlow};
8b8e83329a1datagen2026-10-12 12:01 UTC9
8b8e83329a1datagen2026-10-12 12:01 UTC10
impl Run {
8b8e83329a1datagen2026-10-12 12:01 UTC11
    pub fn jelq(
8b8e83329a1datagen2026-10-12 12:01 UTC12
        &mut self,
8b8e83329a1datagen2026-10-12 12:01 UTC13
        techniques: &[Technique],
8b8e83329a1datagen2026-10-12 12:01 UTC14
        intensity: i32,
8b8e83329a1datagen2026-10-12 12:01 UTC15
        lube: Option<Lube>,
8b8e83329a1datagen2026-10-12 12:01 UTC16
    ) -> Result<Outcome, Problem> {
a11497af08aaatagen2026-10-13 12:04 UTC17
        if techniques != [Technique::Utility(Utility::Energy)] {
a11497af08aaatagen2026-10-13 12:04 UTC18
            self.check_exhausted()?;
a11497af08aaatagen2026-10-13 12:04 UTC19
        }
8b8e83329a1datagen2026-10-12 12:01 UTC20
        if self.online.story == Story::Pending {
8b8e83329a1datagen2026-10-12 12:01 UTC21
            return Ok(self.branch_offer());
8b8e83329a1datagen2026-10-12 12:01 UTC22
        }
8b8e83329a1datagen2026-10-12 12:01 UTC23
        if self.floor == Floor::Forum {
8b8e83329a1datagen2026-10-12 12:01 UTC24
            return Ok(Outcome::json(
8b8e83329a1datagen2026-10-12 12:01 UTC25
                StatusCode::SEE_OTHER,
8b8e83329a1datagen2026-10-12 12:01 UTC26
                json!({ "detail": "Refer to the forum." }),
8b8e83329a1datagen2026-10-12 12:01 UTC27
            )
a11497af08aaatagen2026-10-13 12:04 UTC28
            .header(header::LOCATION, self.path("/thread")));
8b8e83329a1datagen2026-10-12 12:01 UTC29
        }
4f54028cba69atagen2026-10-14 11:45 UTC30
        self.check_cage()?;
8b8e83329a1datagen2026-10-12 12:01 UTC31
        match *techniques {
8b8e83329a1datagen2026-10-12 12:01 UTC32
            [Technique::Utility(utility)] => self.utility(utility, lube),
8b8e83329a1datagen2026-10-12 12:01 UTC33
            [Technique::Training(training)] => self.single(training, intensity, lube),
8b8e83329a1datagen2026-10-12 12:01 UTC34
            [Technique::Training(a), Technique::Training(b)] => self.combo(a, b, intensity, lube),
8b8e83329a1datagen2026-10-12 12:01 UTC35
            [_, _] => Err(Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC36
                StatusCode::BAD_REQUEST,
8b8e83329a1datagen2026-10-12 12:01 UTC37
                "combo-invalid",
8b8e83329a1datagen2026-10-12 12:01 UTC38
                "Only training techniques can be combined.",
8b8e83329a1datagen2026-10-12 12:01 UTC39
            )),
8b8e83329a1datagen2026-10-12 12:01 UTC40
            _ => Err(Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC41
                StatusCode::BAD_REQUEST,
8b8e83329a1datagen2026-10-12 12:01 UTC42
                "too-many-techniques",
8b8e83329a1datagen2026-10-12 12:01 UTC43
                "Two techniques at most. The server has limits even if you do not.",
8b8e83329a1datagen2026-10-12 12:01 UTC44
            )),
8b8e83329a1datagen2026-10-12 12:01 UTC45
        }
8b8e83329a1datagen2026-10-12 12:01 UTC46
    }
8b8e83329a1datagen2026-10-12 12:01 UTC47
8b8e83329a1datagen2026-10-12 12:01 UTC48
    pub(super) fn utility(
8b8e83329a1datagen2026-10-12 12:01 UTC49
        &mut self,
8b8e83329a1datagen2026-10-12 12:01 UTC50
        utility: Utility,
8b8e83329a1datagen2026-10-12 12:01 UTC51
        lube: Option<Lube>,
8b8e83329a1datagen2026-10-12 12:01 UTC52
    ) -> Result<Outcome, Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC53
        match utility {
8b8e83329a1datagen2026-10-12 12:01 UTC54
            Utility::Warmup => return self.warm_up(),
8b8e83329a1datagen2026-10-12 12:01 UTC55
            Utility::Ice => return self.ice(),
8b8e83329a1datagen2026-10-12 12:01 UTC56
            Utility::Supplement => return self.supplement(),
8b8e83329a1datagen2026-10-12 12:01 UTC57
            Utility::Meditate => return self.meditate(),
8b8e83329a1datagen2026-10-12 12:01 UTC58
            Utility::Energy => return self.energy(),
8b8e83329a1datagen2026-10-12 12:01 UTC59
            Utility::Edge | Utility::Pump => {}
8b8e83329a1datagen2026-10-12 12:01 UTC60
        }
bad9f5b8202fatagen2026-10-14 09:23 UTC61
        let lube = lube.filter(|_| utility == Utility::Edge);
8b8e83329a1datagen2026-10-12 12:01 UTC62
        self.ready(Technique::Utility(utility), lube)?;
8b8e83329a1datagen2026-10-12 12:01 UTC63
        self.check_storage()?;
bad9f5b8202fatagen2026-10-14 09:23 UTC64
        self.lube_available(lube)?;
6033b0db186catagen2026-10-13 12:44 UTC65
        self.check_clock()?;
8b8e83329a1datagen2026-10-12 12:01 UTC66
        self.floor_enemy()?;
8b8e83329a1datagen2026-10-12 12:01 UTC67
        match utility {
bad9f5b8202fatagen2026-10-14 09:23 UTC68
            Utility::Edge => self.edge(lube),
8b8e83329a1datagen2026-10-12 12:01 UTC69
            _ => self.pump(),
8b8e83329a1datagen2026-10-12 12:01 UTC70
        }
8b8e83329a1datagen2026-10-12 12:01 UTC71
    }
8b8e83329a1datagen2026-10-12 12:01 UTC72
8b8e83329a1datagen2026-10-12 12:01 UTC73
    pub(super) fn single(
8b8e83329a1datagen2026-10-12 12:01 UTC74
        &mut self,
8b8e83329a1datagen2026-10-12 12:01 UTC75
        training: Training,
8b8e83329a1datagen2026-10-12 12:01 UTC76
        intensity: i32,
8b8e83329a1datagen2026-10-12 12:01 UTC77
        lube: Option<Lube>,
8b8e83329a1datagen2026-10-12 12:01 UTC78
    ) -> Result<Outcome, Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC79
        self.ready(Technique::Training(training), lube)?;
8b8e83329a1datagen2026-10-12 12:01 UTC80
        self.check_storage()?;
8b8e83329a1datagen2026-10-12 12:01 UTC81
        self.lube_available(lube)?;
6033b0db186catagen2026-10-13 12:44 UTC82
        self.check_clock()?;
8b8e83329a1datagen2026-10-12 12:01 UTC83
        self.floor_enemy()?;
8b8e83329a1datagen2026-10-12 12:01 UTC84
        self.take_lube(lube)?;
bf5f8cb6f60fatagen2026-10-13 12:10 UTC85
        let cold = self.cold();
bf5f8cb6f60fatagen2026-10-13 12:10 UTC86
        self.chafe(lube, intensity, cold);
3ac624339c98atagen2026-10-13 12:21 UTC87
        self.commit(intensity, lube, 1)?;
bf5f8cb6f60fatagen2026-10-13 12:10 UTC88
        if let ControlFlow::Break(interrupted) = self.mishaps(training, intensity, cold)? {
8b8e83329a1datagen2026-10-12 12:01 UTC89
            return Ok(interrupted);
8b8e83329a1datagen2026-10-12 12:01 UTC90
        }
8b8e83329a1datagen2026-10-12 12:01 UTC91
        if let ControlFlow::Break(interrupted) = self.hazards(intensity)? {
8b8e83329a1datagen2026-10-12 12:01 UTC92
            return Ok(interrupted);
8b8e83329a1datagen2026-10-12 12:01 UTC93
        }
8b8e83329a1datagen2026-10-12 12:01 UTC94
        let outcome = self.roll(training, intensity, lube)?;
8b8e83329a1datagen2026-10-12 12:01 UTC95
        Ok(self.gossip(outcome))
8b8e83329a1datagen2026-10-12 12:01 UTC96
    }
8b8e83329a1datagen2026-10-12 12:01 UTC97
8b8e83329a1datagen2026-10-12 12:01 UTC98
    pub(super) fn combo(
8b8e83329a1datagen2026-10-12 12:01 UTC99
        &mut self,
8b8e83329a1datagen2026-10-12 12:01 UTC100
        a: Training,
8b8e83329a1datagen2026-10-12 12:01 UTC101
        b: Training,
8b8e83329a1datagen2026-10-12 12:01 UTC102
        intensity: i32,
8b8e83329a1datagen2026-10-12 12:01 UTC103
        lube: Option<Lube>,
8b8e83329a1datagen2026-10-12 12:01 UTC104
    ) -> Result<Outcome, Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC105
        if a == b {
8b8e83329a1datagen2026-10-12 12:01 UTC106
            return Err(Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC107
                StatusCode::BAD_REQUEST,
8b8e83329a1datagen2026-10-12 12:01 UTC108
                "combo-invalid",
8b8e83329a1datagen2026-10-12 12:01 UTC109
                "A technique cannot be combined with itself. The server has checked.",
8b8e83329a1datagen2026-10-12 12:01 UTC110
            ));
8b8e83329a1datagen2026-10-12 12:01 UTC111
        }
8b8e83329a1datagen2026-10-12 12:01 UTC112
        self.ready(Technique::Training(a), lube)?;
8b8e83329a1datagen2026-10-12 12:01 UTC113
        self.ready(Technique::Training(b), lube)?;
8b8e83329a1datagen2026-10-12 12:01 UTC114
        self.check_storage()?;
8b8e83329a1datagen2026-10-12 12:01 UTC115
        self.lube_available(lube)?;
6033b0db186catagen2026-10-13 12:44 UTC116
        self.check_clock()?;
8b8e83329a1datagen2026-10-12 12:01 UTC117
        self.floor_enemy()?;
8b8e83329a1datagen2026-10-12 12:01 UTC118
        self.take_lube(lube)?;
bf5f8cb6f60fatagen2026-10-13 12:10 UTC119
        let cold = self.cold();
bf5f8cb6f60fatagen2026-10-13 12:10 UTC120
        self.chafe(lube, intensity, cold);
bf5f8cb6f60fatagen2026-10-13 12:10 UTC121
        self.chafe(lube, intensity, cold);
3ac624339c98atagen2026-10-13 12:21 UTC122
        self.commit(intensity, lube, 2)?;
8b8e83329a1datagen2026-10-12 12:01 UTC123
        for training in [a, b] {
bf5f8cb6f60fatagen2026-10-13 12:10 UTC124
            if let ControlFlow::Break(interrupted) = self.mishaps(training, intensity, cold)? {
8b8e83329a1datagen2026-10-12 12:01 UTC125
                return Ok(interrupted);
8b8e83329a1datagen2026-10-12 12:01 UTC126
            }
8b8e83329a1datagen2026-10-12 12:01 UTC127
        }
8b8e83329a1datagen2026-10-12 12:01 UTC128
        if let ControlFlow::Break(interrupted) = self.hazards(intensity)? {
8b8e83329a1datagen2026-10-12 12:01 UTC129
            return Ok(interrupted);
8b8e83329a1datagen2026-10-12 12:01 UTC130
        }
8b8e83329a1datagen2026-10-12 12:01 UTC131
98432b6057aaatagen2026-10-14 09:03 UTC132
        let mut reports = Vec::new();
8b8e83329a1datagen2026-10-12 12:01 UTC133
        for training in [a, b] {
8b8e83329a1datagen2026-10-12 12:01 UTC134
            let result = self.roll(training, intensity, lube);
8b8e83329a1datagen2026-10-12 12:01 UTC135
            if self.ended.is_some() {
8b8e83329a1datagen2026-10-12 12:01 UTC136
                return result;
8b8e83329a1datagen2026-10-12 12:01 UTC137
            }
98432b6057aaatagen2026-10-14 09:03 UTC138
            reports.push((
98432b6057aaatagen2026-10-14 09:03 UTC139
                training,
98432b6057aaatagen2026-10-14 09:03 UTC140
                Report::of(result.map(|outcome| self.gossip(outcome))),
98432b6057aaatagen2026-10-14 09:03 UTC141
            ));
8b8e83329a1datagen2026-10-12 12:01 UTC142
        }
8b8e83329a1datagen2026-10-12 12:01 UTC143
8b8e83329a1datagen2026-10-12 12:01 UTC144
        let mut xml = String::from(
27256acf2780atagen2026-10-14 13:42 UTC145
            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<D:multistatus xmlns:D=\"DAV:\" xmlns:J=\"https://jelq.dev/\">\n",
8b8e83329a1datagen2026-10-12 12:01 UTC146
        );
98432b6057aaatagen2026-10-14 09:03 UTC147
        for (training, report) in reports {
27256acf2780atagen2026-10-14 13:42 UTC148
            report.write(
27256acf2780atagen2026-10-14 13:42 UTC149
                &mut xml,
27256acf2780atagen2026-10-14 13:42 UTC150
                &format!("{}?technique={}", self.path(""), training.name()),
8b8e83329a1datagen2026-10-12 12:01 UTC151
            );
8b8e83329a1datagen2026-10-12 12:01 UTC152
        }
8b8e83329a1datagen2026-10-12 12:01 UTC153
        xml.push_str(
8b8e83329a1datagen2026-10-12 12:01 UTC154
            "  <D:responsedescription>Each technique has been reported separately.</D:responsedescription>\n</D:multistatus>\n",
8b8e83329a1datagen2026-10-12 12:01 UTC155
        );
8b8e83329a1datagen2026-10-12 12:01 UTC156
        Ok(Outcome::raw(
8b8e83329a1datagen2026-10-12 12:01 UTC157
            StatusCode::MULTI_STATUS,
8b8e83329a1datagen2026-10-12 12:01 UTC158
            "application/xml; charset=utf-8",
8b8e83329a1datagen2026-10-12 12:01 UTC159
            xml,
8b8e83329a1datagen2026-10-12 12:01 UTC160
        ))
8b8e83329a1datagen2026-10-12 12:01 UTC161
    }
8b8e83329a1datagen2026-10-12 12:01 UTC162
}
98432b6057aaatagen2026-10-14 09:03 UTC163
98432b6057aaatagen2026-10-14 09:03 UTC164
/// What one technique of a combo would have answered on its own.
98432b6057aaatagen2026-10-14 09:03 UTC165
struct Report {
98432b6057aaatagen2026-10-14 09:03 UTC166
    status: StatusCode,
27256acf2780atagen2026-10-14 13:42 UTC167
    error: Option<&'static str>,
27256acf2780atagen2026-10-14 13:42 UTC168
    detail: Option<String>,
27256acf2780atagen2026-10-14 13:42 UTC169
    fields: Vec<(String, String)>,
98432b6057aaatagen2026-10-14 09:03 UTC170
    location: Option<String>,
98432b6057aaatagen2026-10-14 09:03 UTC171
}
98432b6057aaatagen2026-10-14 09:03 UTC172
98432b6057aaatagen2026-10-14 09:03 UTC173
impl Report {
98432b6057aaatagen2026-10-14 09:03 UTC174
    fn of(result: Result<Outcome, Problem>) -> Self {
27256acf2780atagen2026-10-14 13:42 UTC175
        let (status, error, location, body) = match result {
27256acf2780atagen2026-10-14 13:42 UTC176
            Ok(outcome) => (
27256acf2780atagen2026-10-14 13:42 UTC177
                outcome.status(),
27256acf2780atagen2026-10-14 13:42 UTC178
                None,
27256acf2780atagen2026-10-14 13:42 UTC179
                outcome.header_value(&header::LOCATION).map(str::to_owned),
27256acf2780atagen2026-10-14 13:42 UTC180
                outcome.body().and_then(Value::as_object).cloned(),
27256acf2780atagen2026-10-14 13:42 UTC181
            ),
27256acf2780atagen2026-10-14 13:42 UTC182
            Err(problem) => {
27256acf2780atagen2026-10-14 13:42 UTC183
                let mut document = problem.document();
27256acf2780atagen2026-10-14 13:42 UTC184
                for repeated in ["type", "title", "status"] {
27256acf2780atagen2026-10-14 13:42 UTC185
                    document.remove(repeated);
27256acf2780atagen2026-10-14 13:42 UTC186
                }
27256acf2780atagen2026-10-14 13:42 UTC187
                (problem.status(), Some(problem.slug()), None, Some(document))
27256acf2780atagen2026-10-14 13:42 UTC188
            }
27256acf2780atagen2026-10-14 13:42 UTC189
        };
27256acf2780atagen2026-10-14 13:42 UTC190
        let mut body = body.unwrap_or_default();
27256acf2780atagen2026-10-14 13:42 UTC191
        let detail = body
27256acf2780atagen2026-10-14 13:42 UTC192
            .remove("detail")
27256acf2780atagen2026-10-14 13:42 UTC193
            .and_then(|detail| detail.as_str().map(str::to_owned));
27256acf2780atagen2026-10-14 13:42 UTC194
        let fields = body
27256acf2780atagen2026-10-14 13:42 UTC195
            .into_iter()
27256acf2780atagen2026-10-14 13:42 UTC196
            .map(|(key, value)| match value {
27256acf2780atagen2026-10-14 13:42 UTC197
                Value::String(text) => (key, text),
27256acf2780atagen2026-10-14 13:42 UTC198
                other => (key, other.to_string()),
27256acf2780atagen2026-10-14 13:42 UTC199
            })
27256acf2780atagen2026-10-14 13:42 UTC200
            .collect();
27256acf2780atagen2026-10-14 13:42 UTC201
        Self {
27256acf2780atagen2026-10-14 13:42 UTC202
            status,
27256acf2780atagen2026-10-14 13:42 UTC203
            error,
27256acf2780atagen2026-10-14 13:42 UTC204
            detail,
27256acf2780atagen2026-10-14 13:42 UTC205
            fields,
27256acf2780atagen2026-10-14 13:42 UTC206
            location,
27256acf2780atagen2026-10-14 13:42 UTC207
        }
27256acf2780atagen2026-10-14 13:42 UTC208
    }
27256acf2780atagen2026-10-14 13:42 UTC209
27256acf2780atagen2026-10-14 13:42 UTC210
    /// In RFC 4918's order, with anything the game adds after it under its own namespace.
27256acf2780atagen2026-10-14 13:42 UTC211
    fn write(&self, xml: &mut String, href: &str) {
27256acf2780atagen2026-10-14 13:42 UTC212
        let _ = write!(
27256acf2780atagen2026-10-14 13:42 UTC213
            xml,
27256acf2780atagen2026-10-14 13:42 UTC214
            "  <D:response>\n    <D:href>{}</D:href>\n    <D:status>HTTP/1.1 {} {}</D:status>\n",
27256acf2780atagen2026-10-14 13:42 UTC215
            escape(href),
27256acf2780atagen2026-10-14 13:42 UTC216
            self.status.as_u16(),
27256acf2780atagen2026-10-14 13:42 UTC217
            crate::problem::reason(self.status),
27256acf2780atagen2026-10-14 13:42 UTC218
        );
27256acf2780atagen2026-10-14 13:42 UTC219
        if let Some(error) = self.error {
27256acf2780atagen2026-10-14 13:42 UTC220
            let _ = writeln!(xml, "    <D:error><J:{error}/></D:error>");
27256acf2780atagen2026-10-14 13:42 UTC221
        }
27256acf2780atagen2026-10-14 13:42 UTC222
        if let Some(detail) = &self.detail {
27256acf2780atagen2026-10-14 13:42 UTC223
            let _ = writeln!(
27256acf2780atagen2026-10-14 13:42 UTC224
                xml,
27256acf2780atagen2026-10-14 13:42 UTC225
                "    <D:responsedescription>{}</D:responsedescription>",
27256acf2780atagen2026-10-14 13:42 UTC226
                escape(detail)
27256acf2780atagen2026-10-14 13:42 UTC227
            );
27256acf2780atagen2026-10-14 13:42 UTC228
        }
27256acf2780atagen2026-10-14 13:42 UTC229
        if let Some(location) = &self.location {
27256acf2780atagen2026-10-14 13:42 UTC230
            let _ = writeln!(
27256acf2780atagen2026-10-14 13:42 UTC231
                xml,
27256acf2780atagen2026-10-14 13:42 UTC232
                "    <D:location><D:href>{}</D:href></D:location>",
27256acf2780atagen2026-10-14 13:42 UTC233
                escape(location)
27256acf2780atagen2026-10-14 13:42 UTC234
            );
27256acf2780atagen2026-10-14 13:42 UTC235
        }
27256acf2780atagen2026-10-14 13:42 UTC236
        for (key, value) in &self.fields {
27256acf2780atagen2026-10-14 13:42 UTC237
            let _ = writeln!(xml, "    <J:{key}>{}</J:{key}>", escape(value));
98432b6057aaatagen2026-10-14 09:03 UTC238
        }
27256acf2780atagen2026-10-14 13:42 UTC239
        xml.push_str("  </D:response>\n");
98432b6057aaatagen2026-10-14 09:03 UTC240
    }
98432b6057aaatagen2026-10-14 09:03 UTC241
}
98432b6057aaatagen2026-10-14 09:03 UTC242
98432b6057aaatagen2026-10-14 09:03 UTC243
fn escape(text: &str) -> String {
98432b6057aaatagen2026-10-14 09:03 UTC244
    text.replace('&', "&amp;")
98432b6057aaatagen2026-10-14 09:03 UTC245
        .replace('<', "&lt;")
98432b6057aaatagen2026-10-14 09:03 UTC246
        .replace('>', "&gt;")
98432b6057aaatagen2026-10-14 09:03 UTC247
}