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
use common::{
comp::{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, 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>;
}