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,
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
214pub enum WiringActionEffect {
216 SpawnProjectile { constr: ProjectileConstructor },
218 SetBlock { coords: Vec3<i32>, block: Block },
220 SetLight {
222 r: OutputFormula,
223 g: OutputFormula,
224 b: OutputFormula,
225 },
226}
227
228pub struct Wire {
230 pub input: WireNode,
231 pub output: WireNode,
232}
233
234pub 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}