1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
use common::{
    comp::{item::tool, object, Body, LightEmitter, PhysicsState, Pos, ProjectileConstructor},
    event::{EmitExt, ShootEvent},
    terrain::{Block, TerrainChunkSize},
    util::Dir,
    vol::RectVolSize,
};
use common_state::BlockChange;
use hashbrown::HashMap;
use specs::{Component, DenseVecStorage, Entity};
use tracing::warn;
use vek::{num_traits::ToPrimitive, Rgb, Vec3};

/// Represents a logical operation based on a `left` and `right` input. The
/// available kinds of logical operations are enumerated by `LogicKind`.
pub struct Logic {
    pub kind: LogicKind,
    pub left: OutputFormula,
    pub right: OutputFormula,
}

/// The basic element of the wiring system. Inputs are dynamically added based
/// on the outputs of other elements. Actions specify what to output or what
/// inputs to read as well as what effects the values should have on the world
/// state (eg. emit a projectile).
pub struct WiringElement {
    pub inputs: HashMap<String, f32>,
    pub outputs: HashMap<String, OutputFormula>,
    pub actions: Vec<WiringAction>,
}

/// Connects input to output elements. Required for elements to receive outputs
/// from the proper inputs.
pub struct Circuit {
    pub wires: Vec<Wire>,
}

impl Circuit {
    pub fn new(wires: Vec<Wire>) -> Self { Self { wires } }
}

/// Represents an output for a `WiringAction`. The total output can be constant,
/// directly from an input, based on collision state, or based on custom logic.
pub enum OutputFormula {
    /// Returns a constant value
    Constant { value: f32 },
    /// Retrieves the value from a string identified input. A wiring element can
    /// have multiple inputs.
    Input { name: String },
    /// Performs a logic operation on the `left` and `right` values of the
    /// provided `Logic`. The operation is specified by the `LogicKind`.
    /// Operations include `Min`, `Max`, `Sub`, `Sum`, and `Mul`.
    Logic(Box<Logic>),
    /// Returns `value` if the wiring element is in contact with another entity
    /// with collision.
    OnCollide { value: f32 },
    /// Returns `value` if an entity died in the last tick within `radius` of
    /// the wiring element.
    OnDeath { value: f32, radius: f32 },

    // TODO: The following `OutputFormula`s are unimplemented!!!!
    /// Returns an oscillating value based on the sine wave with `amplitude` and
    /// `frequency`.
    SineWave { amplitude: f32, frequency: f32 },
    /// Returns `value` when the wiring element in interacted with.
    OnInteract { value: f32 },
}

impl OutputFormula {
    /// Computes the output of an `OutputFormula` as an `f32` based on the
    /// inputs and world state. Currently that world state only includes
    /// physics state, position, and the list of entities that died in the
    /// last tick.
    pub fn compute_output(
        &self,
        inputs: &HashMap<String, f32>,
        physics_state: Option<&PhysicsState>,
        entities_died_last_tick: &Vec<(Entity, Pos)>,
        pos: Option<&Pos>,
    ) -> f32 {
        match self {
            OutputFormula::Constant { value } => *value,
            OutputFormula::Input { name } => *inputs.get(name).unwrap_or(&0.0),
            OutputFormula::Logic(logic) => {
                let left =
                    &logic
                        .left
                        .compute_output(inputs, physics_state, entities_died_last_tick, pos);
                let right = &logic.right.compute_output(
                    inputs,
                    physics_state,
                    entities_died_last_tick,
                    pos,
                );
                match logic.kind {
                    LogicKind::Max => f32::max(*left, *right),
                    LogicKind::Min => f32::min(*left, *right),
                    LogicKind::Sub => left - right,
                    LogicKind::Sum => left + right,
                    LogicKind::Mul => left * right,
                }
            },
            OutputFormula::OnCollide { value } => physics_state.map_or(0.0, |ps| {
                if ps.touch_entities.is_empty() {
                    0.0
                } else {
                    *value
                }
            }),
            OutputFormula::SineWave { .. } => {
                warn!("Not implemented OutputFormula::SineWave");
                0.0
            },
            OutputFormula::OnInteract { .. } => {
                warn!("Not implemented OutputFormula::OnInteract");
                0.0
            },
            OutputFormula::OnDeath { value, radius } => pos.map_or(0.0, |e_pos| {
                *value
                    * entities_died_last_tick
                        .iter()
                        .filter(|(_, dead_pos)| e_pos.0.distance(dead_pos.0) <= *radius)
                        .count()
                        .to_f32()
                        .unwrap_or(0.0)
            }),
        }
    }
}

/// Logical operations applied to two floats.
pub enum LogicKind {
    /// Returns the minimum of `left` and `right`. Acts like And.
    Min,
    /// Returns the maximum of `left` and `right`. Acts like Or.
    Max,
    /// Returns `left` minus `right`. Acts like Not, depending on referance
    /// values.
    Sub,
    /// Returns `left` plus `right`.
    Sum,
    /// Returns `left` times `right`.
    Mul,
}

/// Determines what kind of output an element produces (or input is read) based
/// on the `formula`. The `threshold` is the minimum computed output for effects
/// to take place. Effects refer to effects in the game world such as emitting
/// light.
pub struct WiringAction {
    pub formula: OutputFormula,
    pub threshold: f32,
    pub effects: Vec<WiringActionEffect>,
}

impl WiringAction {
    /// Applies all effects on the world (such as turning on a light etc.) if
    /// the output of the `formula` is greater than `threshold`.
    pub fn apply_effects(
        &self,
        entity: Entity,
        inputs: &HashMap<String, f32>,
        physics_state: Option<&PhysicsState>,
        entities_died_last_tick: &Vec<(Entity, Pos)>,
        emitters: &mut impl EmitExt<ShootEvent>,
        pos: Option<&Pos>,
        block_change: &mut BlockChange,
        mut light_emitter: Option<&mut LightEmitter>,
    ) {
        self.effects
            .iter()
            .for_each(|action_effect| match action_effect {
                WiringActionEffect::SetBlock { coords, block } => {
                    let chunk_origin = pos.map_or(Vec3::zero(), |opos| {
                        opos.0
                            .xy()
                            .as_::<i32>()
                            .map2(TerrainChunkSize::RECT_SIZE.as_::<i32>(), |a, b| (a / b) * b)
                            .with_z(0)
                    });
                    let offset_pos = chunk_origin + coords;
                    block_change.set(offset_pos, *block);
                },
                WiringActionEffect::SpawnProjectile { constr } => {
                    if let Some(&pos) = pos {
                        emitters.emit(ShootEvent {
                            entity,
                            pos,
                            dir: Dir::forward(),
                            body: Body::Object(object::Body::Arrow),
                            projectile: constr.create_projectile(
                                None,
                                1.0,
                                tool::Stats::one(),
                                None,
                            ),
                            light: None,
                            speed: 5.0,
                            object: None,
                        });
                    }
                },
                WiringActionEffect::SetLight { r, g, b } => {
                    if let Some(light_emitter) = &mut light_emitter {
                        let computed_r =
                            r.compute_output(inputs, physics_state, entities_died_last_tick, pos);
                        let computed_g =
                            g.compute_output(inputs, physics_state, entities_died_last_tick, pos);
                        let computed_b =
                            b.compute_output(inputs, physics_state, entities_died_last_tick, pos);

                        light_emitter.col = Rgb::new(computed_r, computed_g, computed_b);
                    }
                },
            });
    }
}

/// Effects of a circuit in the game world.
pub enum WiringActionEffect {
    /// Spawn a projectile.
    SpawnProjectile { constr: ProjectileConstructor },
    /// Set a terrain block at the provided coordinates.
    SetBlock { coords: Vec3<i32>, block: Block },
    /// Emit light with the given RGB values.
    SetLight {
        r: OutputFormula,
        g: OutputFormula,
        b: OutputFormula,
    },
}

/// Holds an input and output node.
pub struct Wire {
    pub input: WireNode,
    pub output: WireNode,
}

/// Represents a node in the circuit. Each node is an entity with a name.
pub struct WireNode {
    pub entity: Entity,
    pub name: String,
}

impl WireNode {
    pub fn new(entity: Entity, name: String) -> Self { Self { entity, name } }
}

impl Component for WiringElement {
    type Storage = DenseVecStorage<Self>;
}

impl Component for Circuit {
    type Storage = DenseVecStorage<Self>;
}