use common::{ comp::{item::tool, object, Body, LightEmitter, PhysicsState, Pos, ProjectileConstructor}, event::{Emitter, ServerEvent}, 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, pub outputs: HashMap, pub actions: Vec, } /// Connects input to output elements. Required for elements to receive outputs /// from the proper inputs. pub struct Circuit { pub wires: Vec, } impl Circuit { pub fn new(wires: Vec) -> 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), /// 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, 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, } 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, physics_state: Option<&PhysicsState>, entities_died_last_tick: &Vec<(Entity, Pos)>, server_emitter: &mut Emitter<'_, ServerEvent>, 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_::() .map2(TerrainChunkSize::RECT_SIZE.as_::(), |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 { server_emitter.emit(ServerEvent::Shoot { 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, 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; } impl Component for Circuit { type Storage = DenseVecStorage; }