veloren_server/
wiring.rs

1use common::{
2    comp::{Body, LightEmitter, PhysicsState, Pos, ProjectileConstructor, object},
3    event::{EmitExt, ShootEvent},
4    terrain::{Block, TerrainChunkSize},
5    util::Dir,
6    vol::RectVolSize,
7};
8use common_state::BlockChange;
9use hashbrown::HashMap;
10use specs::{Component, DenseVecStorage, Entity};
11use tracing::warn;
12use vek::{Rgb, Vec3, num_traits::ToPrimitive};
13
14/// Represents a logical operation based on a `left` and `right` input. The
15/// available kinds of logical operations are enumerated by `LogicKind`.
16pub struct Logic {
17    pub kind: LogicKind,
18    pub left: OutputFormula,
19    pub right: OutputFormula,
20}
21
22/// The basic element of the wiring system. Inputs are dynamically added based
23/// on the outputs of other elements. Actions specify what to output or what
24/// inputs to read as well as what effects the values should have on the world
25/// state (eg. emit a projectile).
26pub struct WiringElement {
27    pub inputs: HashMap<String, f32>,
28    pub outputs: HashMap<String, OutputFormula>,
29    pub actions: Vec<WiringAction>,
30}
31
32/// Connects input to output elements. Required for elements to receive outputs
33/// from the proper inputs.
34pub struct Circuit {
35    pub wires: Vec<Wire>,
36}
37
38impl Circuit {
39    pub fn new(wires: Vec<Wire>) -> Self { Self { wires } }
40}
41
42/// Represents an output for a `WiringAction`. The total output can be constant,
43/// directly from an input, based on collision state, or based on custom logic.
44pub enum OutputFormula {
45    /// Returns a constant value
46    Constant { value: f32 },
47    /// Retrieves the value from a string identified input. A wiring element can
48    /// have multiple inputs.
49    Input { name: String },
50    /// Performs a logic operation on the `left` and `right` values of the
51    /// provided `Logic`. The operation is specified by the `LogicKind`.
52    /// Operations include `Min`, `Max`, `Sub`, `Sum`, and `Mul`.
53    Logic(Box<Logic>),
54    /// Returns `value` if the wiring element is in contact with another entity
55    /// with collision.
56    OnCollide { value: f32 },
57    /// Returns `value` if an entity died in the last tick within `radius` of
58    /// the wiring element.
59    OnDeath { value: f32, radius: f32 },
60
61    // TODO: The following `OutputFormula`s are unimplemented!!!!
62    /// Returns an oscillating value based on the sine wave with `amplitude` and
63    /// `frequency`.
64    SineWave { amplitude: f32, frequency: f32 },
65    /// Returns `value` when the wiring element in interacted with.
66    OnInteract { value: f32 },
67}
68
69impl OutputFormula {
70    /// Computes the output of an `OutputFormula` as an `f32` based on the
71    /// inputs and world state. Currently that world state only includes
72    /// physics state, position, and the list of entities that died in the
73    /// last tick.
74    pub fn compute_output(
75        &self,
76        inputs: &HashMap<String, f32>,
77        physics_state: Option<&PhysicsState>,
78        entities_died_last_tick: &Vec<(Entity, Pos)>,
79        pos: Option<&Pos>,
80    ) -> f32 {
81        match self {
82            OutputFormula::Constant { value } => *value,
83            OutputFormula::Input { name } => *inputs.get(name).unwrap_or(&0.0),
84            OutputFormula::Logic(logic) => {
85                let left =
86                    &logic
87                        .left
88                        .compute_output(inputs, physics_state, entities_died_last_tick, pos);
89                let right = &logic.right.compute_output(
90                    inputs,
91                    physics_state,
92                    entities_died_last_tick,
93                    pos,
94                );
95                match logic.kind {
96                    LogicKind::Max => f32::max(*left, *right),
97                    LogicKind::Min => f32::min(*left, *right),
98                    LogicKind::Sub => left - right,
99                    LogicKind::Sum => left + right,
100                    LogicKind::Mul => left * right,
101                }
102            },
103            OutputFormula::OnCollide { value } => physics_state.map_or(0.0, |ps| {
104                if ps.touch_entities.is_empty() {
105                    0.0
106                } else {
107                    *value
108                }
109            }),
110            OutputFormula::SineWave { .. } => {
111                warn!("Not implemented OutputFormula::SineWave");
112                0.0
113            },
114            OutputFormula::OnInteract { .. } => {
115                warn!("Not implemented OutputFormula::OnInteract");
116                0.0
117            },
118            OutputFormula::OnDeath { value, radius } => pos.map_or(0.0, |e_pos| {
119                *value
120                    * entities_died_last_tick
121                        .iter()
122                        .filter(|(_, dead_pos)| e_pos.0.distance(dead_pos.0) <= *radius)
123                        .count()
124                        .to_f32()
125                        .unwrap_or(0.0)
126            }),
127        }
128    }
129}
130
131/// Logical operations applied to two floats.
132pub enum LogicKind {
133    /// Returns the minimum of `left` and `right`. Acts like And.
134    Min,
135    /// Returns the maximum of `left` and `right`. Acts like Or.
136    Max,
137    /// Returns `left` minus `right`. Acts like Not, depending on referance
138    /// values.
139    Sub,
140    /// Returns `left` plus `right`.
141    Sum,
142    /// Returns `left` times `right`.
143    Mul,
144}
145
146/// Determines what kind of output an element produces (or input is read) based
147/// on the `formula`. The `threshold` is the minimum computed output for effects
148/// to take place. Effects refer to effects in the game world such as emitting
149/// light.
150pub struct WiringAction {
151    pub formula: OutputFormula,
152    pub threshold: f32,
153    pub effects: Vec<WiringActionEffect>,
154}
155
156impl WiringAction {
157    /// Applies all effects on the world (such as turning on a light etc.) if
158    /// the output of the `formula` is greater than `threshold`.
159    pub fn apply_effects(
160        &self,
161        entity: Entity,
162        inputs: &HashMap<String, f32>,
163        physics_state: Option<&PhysicsState>,
164        entities_died_last_tick: &Vec<(Entity, Pos)>,
165        emitters: &mut impl EmitExt<ShootEvent>,
166        pos: Option<&Pos>,
167        block_change: &mut BlockChange,
168        mut light_emitter: Option<&mut LightEmitter>,
169    ) {
170        self.effects
171            .iter()
172            .for_each(|action_effect| match action_effect {
173                WiringActionEffect::SetBlock { coords, block } => {
174                    let chunk_origin = pos.map_or(Vec3::zero(), |opos| {
175                        opos.0
176                            .xy()
177                            .as_::<i32>()
178                            .map2(TerrainChunkSize::RECT_SIZE.as_::<i32>(), |a, b| (a / b) * b)
179                            .with_z(0)
180                    });
181                    let offset_pos = chunk_origin + coords;
182                    block_change.set(offset_pos, *block);
183                },
184                WiringActionEffect::SpawnProjectile { constr } => {
185                    if let Some(&pos) = pos {
186                        emitters.emit(ShootEvent {
187                            entity,
188                            pos,
189                            dir: Dir::forward(),
190                            body: Body::Object(object::Body::Arrow),
191                            projectile: constr.create_projectile(None, 1.0, None),
192                            light: None,
193                            speed: 5.0,
194                            object: None,
195                        });
196                    }
197                },
198                WiringActionEffect::SetLight { r, g, b } => {
199                    if let Some(light_emitter) = &mut light_emitter {
200                        let computed_r =
201                            r.compute_output(inputs, physics_state, entities_died_last_tick, pos);
202                        let computed_g =
203                            g.compute_output(inputs, physics_state, entities_died_last_tick, pos);
204                        let computed_b =
205                            b.compute_output(inputs, physics_state, entities_died_last_tick, pos);
206
207                        light_emitter.col = Rgb::new(computed_r, computed_g, computed_b);
208                    }
209                },
210            });
211    }
212}
213
214/// Effects of a circuit in the game world.
215pub enum WiringActionEffect {
216    /// Spawn a projectile.
217    SpawnProjectile { constr: ProjectileConstructor },
218    /// Set a terrain block at the provided coordinates.
219    SetBlock { coords: Vec3<i32>, block: Block },
220    /// Emit light with the given RGB values.
221    SetLight {
222        r: OutputFormula,
223        g: OutputFormula,
224        b: OutputFormula,
225    },
226}
227
228/// Holds an input and output node.
229pub struct Wire {
230    pub input: WireNode,
231    pub output: WireNode,
232}
233
234/// Represents a node in the circuit. Each node is an entity with a name.
235pub struct WireNode {
236    pub entity: Entity,
237    pub name: String,
238}
239
240impl WireNode {
241    pub fn new(entity: Entity, name: String) -> Self { Self { entity, name } }
242}
243
244impl Component for WiringElement {
245    type Storage = DenseVecStorage<Self>;
246}
247
248impl Component for Circuit {
249    type Storage = DenseVecStorage<Self>;
250}