Skip to main content

delvewright_dsl/
difficulty.rs

1//! The world difficulty a campaign ships at, and the bodies it stages.
2//!
3//! A campaign may declare `world.difficulty` (`easy` / `normal` / `hard`;
4//! `peaceful` is refused, `DW0468`). Absent, the engine **derives** it, and the
5//! derivation never picks a difficulty that removes a body the campaign stages:
6//! peaceful is the only difficulty that removes any body (see
7//! [`crate::registry::peaceful_despawn`]), so the derivation is `easy` when the
8//! campaign fields any wave or stages any body of a type peaceful discards, and
9//! `peaceful` otherwise. One function, read by the emitter (`server.properties`),
10//! the combat arithmetic and the `DW0469` advisory alike.
11
12use crate::envelope::Campaign;
13use crate::{BodyRef, Verb, WorldDifficulty};
14
15/// One body a campaign can put into the world, with the entity type it ships
16/// as.
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct StagedBody {
19    /// What declares it: `wave-mob`, `npc`, `actor`, `actor-twin` (the
20    /// real-AI body an `unleash-actor` summons) or `volley-projectile`.
21    pub what: &'static str,
22    /// The declaring object's id (the wave, NPC or actor id; for a volley, the
23    /// JSON pointer of the `volley` effect).
24    pub id: String,
25    /// The entity type the body ships as.
26    pub entity: String,
27}
28
29/// Every body the campaign stages whose entity type the author chose, in a
30/// fixed order: wave mobs, NPCs, actors (the puppet as it ships — a skinned one
31/// is a mannequin — then the twin when the actor is unleashed), volley
32/// projectiles.
33pub fn staged_bodies(c: &Campaign) -> Vec<StagedBody> {
34    let q = &c.quests.content;
35    let mut out = Vec::new();
36    for w in &q.waves {
37        for m in &w.mobs {
38            out.push(StagedBody {
39                what: "wave-mob",
40                id: w.id.as_str().to_string(),
41                entity: m.entity.clone(),
42            });
43        }
44    }
45    for n in &c.npcs.content.npcs {
46        out.push(StagedBody {
47            what: "npc",
48            id: n.id.as_str().to_string(),
49            entity: BodyRef::Npc(n).worn_entity().to_string(),
50        });
51    }
52    let unleashed = crate::fight::unleashed_actors(c);
53    for a in &q.actors {
54        out.push(StagedBody {
55            what: "actor",
56            id: a.id.as_str().to_string(),
57            entity: BodyRef::Actor(a).worn_entity().to_string(),
58        });
59        if unleashed.contains(a.id.as_str()) {
60            out.push(StagedBody {
61                what: "actor-twin",
62                id: a.id.as_str().to_string(),
63                entity: a.entity.clone(),
64            });
65        }
66    }
67    crate::for_each_campaign_effect(c, &mut |path, _, eff| {
68        if let Verb::Volley { projectile, .. } = &eff.verb {
69            out.push(StagedBody {
70                what: "volley-projectile",
71                id: path.to_string(),
72                entity: projectile
73                    .as_deref()
74                    .unwrap_or(crate::DEFAULT_VOLLEY_PROJECTILE)
75                    .to_string(),
76            });
77        }
78    });
79    out
80}
81
82/// The staged bodies the pinned game would discard on peaceful.
83pub fn bodies_removed_on_peaceful(c: &Campaign) -> Vec<StagedBody> {
84    staged_bodies(c)
85        .into_iter()
86        .filter(|b| crate::registry::removed_on_peaceful(&b.entity))
87        .collect()
88}
89
90/// The difficulty the engine derives for a campaign that declares none: the
91/// lowest that keeps every staged body and every declared fight. `easy` when
92/// the campaign fields any wave or stages a body peaceful discards, else
93/// `peaceful`.
94pub fn derived_difficulty(c: &Campaign) -> WorldDifficulty {
95    if !c.quests.content.waves.is_empty() || !bodies_removed_on_peaceful(c).is_empty() {
96        WorldDifficulty::Easy
97    } else {
98        WorldDifficulty::Peaceful
99    }
100}
101
102/// The difficulty the delve ships at: the declared one, else
103/// [`derived_difficulty`].
104pub fn effective_difficulty(c: &Campaign) -> WorldDifficulty {
105    c.world
106        .content
107        .difficulty
108        .unwrap_or_else(|| derived_difficulty(c))
109}