diff --git a/sidecar/src/main.rs b/sidecar/src/main.rs index df08f77..65efcf7 100644 --- a/sidecar/src/main.rs +++ b/sidecar/src/main.rs @@ -203,6 +203,11 @@ use tracing_subscriber::EnvFilter; /// retry never opens a second; DT despawns its own chest. `dt.started`, `dt.won` and `dt.ended` /// ride the feed like any other event. /// +/// Guarded Crate (§20.9, the module's PLAN_REDESIGNS.md §14.3.5) adds one forward, +/// `POST /gc/place`: place one guarded crate for a run's step. The plugin remembers the key so a +/// retry never places a second; the crate is kept when the run ends (D373). `gc.started`, +/// `gc.ended` and `gc.failed` ride the feed like any other event. +/// /// `docs/rust-link/PROTOCOL.md` is the specification — §8 the read path, §9 identity, §10 the /// mirror, §11 configuration, §12 clans, §13 the raid frame, §14 the leases, §15 the world verbs, /// §16 the rewards, §17 the map, §18 the optional mods, §19 the walk's fixes, §20 the step editor; diff --git a/sidecar/src/web.rs b/sidecar/src/web.rs index a2447c7..7305883 100644 --- a/sidecar/src/web.rs +++ b/sidecar/src/web.rs @@ -128,6 +128,9 @@ pub async fn serve(addr: &str, state: AppState) -> anyhow::Result<()> { // Protocol 14 (§20.8): open one Dangerous Treasures event. The plugin remembers the key; // DT picks the spot and despawns its own chest, so nothing here is kept either. .route("/dt/start", post(dt_start)) + // Protocol 14 (§20.9): place one guarded crate. The plugin remembers the key; the crate is + // kept when the run ends (Guarded Crate cannot end one alone), so nothing is kept here. + .route("/gc/place", post(gc_place)) // Protocol 10 (§29): who took part, what they may redeem, and a line in chat. The tally // is counted and held by the plugin, the kit catalogue is Kits' own, and whether a chat // line was already said is the plugin's memory. These move lines, like everything above. @@ -573,6 +576,13 @@ async fn dt_start(State(st): State, Json(body): Json) -> Respon forward_object(&st, body, "dt.start", "a Dangerous Treasures start").await } +/// Place one guarded crate for a run's step (protocol 14). `gc.placed` (where it landed, and +/// `repeat` for a key already seen) or `gc.error` with a reason, `npcspawn-missing` among them. +/// Guarded Crate ends its own event, so nothing is remembered here. +async fn gc_place(State(st): State, Json(body): Json) -> Response { + forward_object(&st, body, "gc.place", "a guarded crate placement").await +} + /// Start counting who takes part in a run. `tally.ok` or `tally.error` with a reason. async fn tally_open(State(st): State, Json(body): Json) -> Response { forward_object(&st, body, "tally.open", "a tally").await @@ -1319,6 +1329,23 @@ mod tests { assert!(stamp(json!(2), "dt.start", "r-61").is_none()); } + /// Protocol 14, Guarded Crate: a placement is stamped `gc.place` whatever its body names, so + /// the route can place a guarded crate and nothing else, and its key, event and location pass + /// through. + #[test] + fn protocol_14_gc_place_is_stamped() { + let body = json!({ "cmd": "dt.start", "reqId": "theirs", "key": "12:4", "event": "Elite", "monument": "powerplant_1", "offsetX": 10.0 }); + let stamped = stamp(body, "gc.place", "r-62").expect("an object is stamped"); + + assert_eq!(stamped["cmd"], "gc.place"); + assert_eq!(stamped["reqId"], "r-62"); + assert_eq!(stamped["key"], "12:4"); + assert_eq!(stamped["event"], "Elite"); + assert_eq!(stamped["monument"], "powerplant_1"); + assert_eq!(stamped["offsetX"], 10.0); + assert!(stamp(json!("Elite"), "gc.place", "r-63").is_none()); + } + /// Protocol 12: a title set is forwarded whole. The markup inside each `text` is the module's /// and the plugin's business, so it must arrive byte for byte as the website composed it. #[test]