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
14pub struct Logic {
17 pub kind: LogicKind,
18 pub left: OutputFormula,
19 pub right: OutputFormula,
20}
21
22pub struct WiringElement {
27 pub inputs: HashMap<String, f32>,
28 pub outputs: HashMap<String, OutputFormula>,
29 pub actions: Vec<WiringAction>,
30}
31
32pub struct Circuit {
35 pub wires: Vec<Wire>,
36}
37
38impl Circuit {
39 pub fn new(wires: Vec<Wire>) -> Self { Self { wires } }
40}
41
42pub enum OutputFormula {
45 Constant { value: f32 },
47 Input { name: String },
50 Logic(Box<Logic>),
54 OnCollide { value: f32 },
57 OnDeath { value: f32, radius: f32 },
60
61 SineWave { amplitude: f32, frequency: f32 },
65 OnInteract { value: f32 },
67}
68
69impl OutputFormula {
70 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
131pub enum LogicKind {
133 Min,
135 Max,
137 Sub,
140 Sum,
142 Mul,
144}
145
146pub struct WiringAction {
151 pub formula: OutputFormula,
152 pub threshold: f32,
153 pub effects: Vec<WiringActionEffect>,
154}
155
156impl WiringAction {
157 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 let (projectile, marker) =
187 constr.clone().create_projectile(None, 1.0, None, None);
188 emitters.emit(ShootEvent {
189 entity: Some(entity),
190 source_vel: None,
191 pos,
192 dir: Dir::forward(),
193 body: Body::Object(object::Body::Arrow),
194 projectile,
195 light: None,
196 speed: 5.0,
197 object: None,
198 marker,
199 });
200 }
201 },
202 WiringActionEffect::SetLight { r, g, b } => {
203 if let Some(light_emitter) = &mut light_emitter {
204 let computed_r =
205 r.compute_output(inputs, physics_state, entities_died_last_tick, pos);
206 let computed_g =
207 g.compute_output(inputs, physics_state, entities_died_last_tick, pos);
208 let computed_b =
209 b.compute_output(inputs, physics_state, entities_died_last_tick, pos);
210
211 light_emitter.col = Rgb::new(computed_r, computed_g, computed_b);
212 }
213 },
214 });
215 }
216}
217
218pub enum WiringActionEffect {
220 SpawnProjectile { constr: Box<ProjectileConstructor> },
222 SetBlock { coords: Vec3<i32>, block: Block },
224 SetLight {
226 r: OutputFormula,
227 g: OutputFormula,
228 b: OutputFormula,
229 },
230}
231
232pub struct Wire {
234 pub input: WireNode,
235 pub output: WireNode,
236}
237
238pub struct WireNode {
240 pub entity: Entity,
241 pub name: String,
242}
243
244impl WireNode {
245 pub fn new(entity: Entity, name: String) -> Self { Self { entity, name } }
246}
247
248impl Component for WiringElement {
249 type Storage = DenseVecStorage<Self>;
250}
251
252impl Component for Circuit {
253 type Storage = DenseVecStorage<Self>;
254}