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 emitters.emit(ShootEvent {
187 entity: Some(entity),
188 source_vel: None,
189 pos,
190 dir: Dir::forward(),
191 body: Body::Object(object::Body::Arrow),
192 projectile: constr.clone().create_projectile(None, 1.0, None),
193 light: None,
194 speed: 5.0,
195 object: None,
196 marker: None,
197 });
198 }
199 },
200 WiringActionEffect::SetLight { r, g, b } => {
201 if let Some(light_emitter) = &mut light_emitter {
202 let computed_r =
203 r.compute_output(inputs, physics_state, entities_died_last_tick, pos);
204 let computed_g =
205 g.compute_output(inputs, physics_state, entities_died_last_tick, pos);
206 let computed_b =
207 b.compute_output(inputs, physics_state, entities_died_last_tick, pos);
208
209 light_emitter.col = Rgb::new(computed_r, computed_g, computed_b);
210 }
211 },
212 });
213 }
214}
215
216pub enum WiringActionEffect {
218 SpawnProjectile { constr: Box<ProjectileConstructor> },
220 SetBlock { coords: Vec3<i32>, block: Block },
222 SetLight {
224 r: OutputFormula,
225 g: OutputFormula,
226 b: OutputFormula,
227 },
228}
229
230pub struct Wire {
232 pub input: WireNode,
233 pub output: WireNode,
234}
235
236pub struct WireNode {
238 pub entity: Entity,
239 pub name: String,
240}
241
242impl WireNode {
243 pub fn new(entity: Entity, name: String) -> Self { Self { entity, name } }
244}
245
246impl Component for WiringElement {
247 type Storage = DenseVecStorage<Self>;
248}
249
250impl Component for Circuit {
251 type Storage = DenseVecStorage<Self>;
252}