mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'james/triggered' into 'master'
Wiring System Documentation and Refactoring See merge request veloren/veloren!3447
This commit is contained in:
commit
5284e9ec94
@ -11,7 +11,7 @@ use crate::{
|
||||
},
|
||||
sys::terrain::NpcData,
|
||||
wiring,
|
||||
wiring::{Logic, OutputFormula},
|
||||
wiring::OutputFormula,
|
||||
Server, Settings, SpawnPoint, StateExt,
|
||||
};
|
||||
use assets::AssetExt;
|
||||
@ -60,7 +60,7 @@ use specs::{
|
||||
};
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
use vek::*;
|
||||
use wiring::{Circuit, Wire, WiringAction, WiringActionEffect, WiringElement};
|
||||
use wiring::{Circuit, Wire, WireNode, WiringAction, WiringActionEffect, WiringElement};
|
||||
use world::util::Sampler;
|
||||
|
||||
use common::comp::Alignment;
|
||||
@ -2200,118 +2200,58 @@ fn handle_spawn_wiring(
|
||||
_args: Vec<String>,
|
||||
_action: &ServerChatCommand,
|
||||
) -> CmdResult<()> {
|
||||
// Obviously it is a WIP - use it for debug
|
||||
|
||||
let mut pos = position(server, target, "target")?;
|
||||
pos.0.x += 3.0;
|
||||
|
||||
let mut outputs1 = HashMap::new();
|
||||
outputs1.insert(
|
||||
"deaths_last_tick".to_string(),
|
||||
wiring::OutputFormula::OnDeath {
|
||||
value: 1.0,
|
||||
radius: 30.0,
|
||||
},
|
||||
);
|
||||
outputs1.insert(
|
||||
"deaths_accumulated".to_string(),
|
||||
OutputFormula::Logic(Box::new(Logic {
|
||||
kind: wiring::LogicKind::Sum,
|
||||
left: OutputFormula::Logic(Box::new(Logic {
|
||||
kind: wiring::LogicKind::Sub,
|
||||
left: OutputFormula::Input {
|
||||
name: "deaths_accumulated".to_string(),
|
||||
},
|
||||
right: OutputFormula::Logic(Box::new(Logic {
|
||||
kind: wiring::LogicKind::Min,
|
||||
left: OutputFormula::Input {
|
||||
name: "pressed".to_string(),
|
||||
},
|
||||
right: OutputFormula::Input {
|
||||
name: "deaths_accumulated".to_string(),
|
||||
},
|
||||
})),
|
||||
})),
|
||||
right: OutputFormula::Input {
|
||||
name: "deaths_last_tick".to_string(),
|
||||
},
|
||||
})),
|
||||
);
|
||||
outputs1.insert("pressed".to_string(), OutputFormula::OnCollide {
|
||||
value: f32::MAX,
|
||||
outputs1.insert("button".to_string(), OutputFormula::OnCollide {
|
||||
value: 1.0,
|
||||
});
|
||||
|
||||
// Create the first element of the circuit.
|
||||
// This is a coin body. This element does not have any inputs or actions.
|
||||
// Instead there is one output. When there is a collision with this element the
|
||||
// value of 1.0 will be sent as an input with the "button" label. Any
|
||||
// element with an `Input` for the name "button" can use this value as an
|
||||
// input. The threshold does not matter as there are no effects for this
|
||||
// element.
|
||||
let builder1 = server
|
||||
.state
|
||||
.create_wiring(pos, comp::object::Body::Coins, WiringElement {
|
||||
actions: vec![WiringAction {
|
||||
formula: wiring::OutputFormula::Constant { value: 1.0 },
|
||||
threshold: 1.0,
|
||||
effects: vec![WiringActionEffect::SetLight {
|
||||
r: wiring::OutputFormula::Input {
|
||||
name: String::from("color"),
|
||||
},
|
||||
g: wiring::OutputFormula::Input {
|
||||
name: String::from("color"),
|
||||
},
|
||||
b: wiring::OutputFormula::Input {
|
||||
name: String::from("color"),
|
||||
},
|
||||
}],
|
||||
}],
|
||||
inputs: HashMap::new(),
|
||||
outputs: outputs1,
|
||||
actions: Vec::new(),
|
||||
})
|
||||
.with(comp::Density(100_f32))
|
||||
.with(comp::Sticky);
|
||||
.with(comp::Density(100_f32));
|
||||
let ent1 = builder1.build();
|
||||
|
||||
pos.0.x += 3.0;
|
||||
// The second element has no elements in the `inputs` field to start with. When
|
||||
// the circuit runs, the input as specified by the `Input` OutputFormula is
|
||||
// added to the inputs. The next tick the effect(s) are applied based on the
|
||||
// input value.
|
||||
let builder2 = server
|
||||
.state
|
||||
.create_wiring(pos, comp::object::Body::Coins, WiringElement {
|
||||
actions: vec![
|
||||
WiringAction {
|
||||
formula: wiring::OutputFormula::Input {
|
||||
name: String::from("deaths_accumulated"),
|
||||
},
|
||||
threshold: 5.0,
|
||||
effects: vec![WiringActionEffect::SpawnProjectile {
|
||||
constr: comp::ProjectileConstructor::Arrow {
|
||||
damage: 1.0,
|
||||
energy_regen: 0.0,
|
||||
knockback: 0.0,
|
||||
},
|
||||
}],
|
||||
},
|
||||
WiringAction {
|
||||
formula: wiring::OutputFormula::Input {
|
||||
name: String::from("deaths_accumulated"),
|
||||
},
|
||||
threshold: 1.0,
|
||||
effects: vec![WiringActionEffect::SetBlock {
|
||||
coords: vek::Vec3::new(0, 0, pos.0.z as i32),
|
||||
block: Block::new(BlockKind::Water, vek::Rgb::new(0, 0, 0)),
|
||||
}],
|
||||
},
|
||||
WiringAction {
|
||||
formula: wiring::OutputFormula::Constant { value: 1.0 },
|
||||
threshold: 1.0,
|
||||
effects: vec![WiringActionEffect::SetLight {
|
||||
r: wiring::OutputFormula::Input {
|
||||
name: String::from("color"),
|
||||
},
|
||||
g: wiring::OutputFormula::Input {
|
||||
name: String::from("color"),
|
||||
},
|
||||
b: wiring::OutputFormula::Input {
|
||||
name: String::from("color"),
|
||||
},
|
||||
}],
|
||||
},
|
||||
],
|
||||
inputs: HashMap::new(),
|
||||
outputs: HashMap::new(),
|
||||
actions: vec![WiringAction {
|
||||
formula: OutputFormula::Input {
|
||||
name: String::from("button"),
|
||||
},
|
||||
threshold: 0.0,
|
||||
effects: vec![WiringActionEffect::SetLight {
|
||||
r: OutputFormula::Input {
|
||||
name: String::from("button"),
|
||||
},
|
||||
g: OutputFormula::Input {
|
||||
name: String::from("button"),
|
||||
},
|
||||
b: OutputFormula::Input {
|
||||
name: String::from("button"),
|
||||
},
|
||||
}],
|
||||
}],
|
||||
})
|
||||
.with(comp::Density(100_f32));
|
||||
let ent2 = builder2.build();
|
||||
@ -2320,39 +2260,15 @@ fn handle_spawn_wiring(
|
||||
let builder3 = server
|
||||
.state
|
||||
.create_wiring(pos, comp::object::Body::TrainingDummy, WiringElement {
|
||||
actions: vec![],
|
||||
inputs: HashMap::new(),
|
||||
outputs: HashMap::new(),
|
||||
actions: Vec::new(),
|
||||
})
|
||||
.with(comp::Density(comp::object::Body::TrainingDummy.density().0))
|
||||
.with(Circuit {
|
||||
wires: vec![
|
||||
Wire {
|
||||
input_entity: ent1,
|
||||
input_field: String::from("deaths_last_tick"),
|
||||
output_entity: ent1,
|
||||
output_field: String::from("deaths_last_tick"),
|
||||
},
|
||||
Wire {
|
||||
input_entity: ent1,
|
||||
input_field: String::from("deaths_accumulated"),
|
||||
output_entity: ent1,
|
||||
output_field: String::from("deaths_accumulated"),
|
||||
},
|
||||
Wire {
|
||||
input_entity: ent1,
|
||||
input_field: String::from("pressed"),
|
||||
output_entity: ent1,
|
||||
output_field: String::from("pressed"),
|
||||
},
|
||||
Wire {
|
||||
input_entity: ent1,
|
||||
input_field: String::from("deaths_accumulated"),
|
||||
output_entity: ent2,
|
||||
output_field: String::from("deaths_accumulated"),
|
||||
},
|
||||
],
|
||||
});
|
||||
.with(Circuit::new(vec![Wire {
|
||||
input: WireNode::new(ent1, "button".to_string()),
|
||||
output: WireNode::new(ent2, "button".to_string()),
|
||||
}]));
|
||||
builder3.build();
|
||||
|
||||
server.notify_client(
|
||||
|
@ -422,7 +422,7 @@ impl StateExt for State {
|
||||
.with(comp::Ori::default())
|
||||
.with(capsule(&object.into()))
|
||||
.with(comp::Body::Object(object))
|
||||
.with(comp::Mass(10.0))
|
||||
.with(comp::Mass(100.0))
|
||||
// .with(comp::Sticky)
|
||||
.with(wiring_element)
|
||||
.with(comp::LightEmitter {
|
||||
|
@ -11,72 +11,131 @@ use specs::{
|
||||
join::Join, shred::ResourceId, Entities, Entity, Read, ReadStorage, SystemData, World, Write,
|
||||
WriteStorage,
|
||||
};
|
||||
mod compute_outputs;
|
||||
use compute_outputs::compute_outputs;
|
||||
mod dispatch_actions;
|
||||
use dispatch_actions::dispatch_actions;
|
||||
|
||||
#[derive(SystemData)]
|
||||
pub struct WiringData<'a> {
|
||||
pub circuits: ReadStorage<'a, Circuit>,
|
||||
pub wiring_elements: WriteStorage<'a, WiringElement>,
|
||||
|
||||
pub entities: Entities<'a>,
|
||||
|
||||
pub light_emitters: WriteStorage<'a, LightEmitter>, // maybe
|
||||
pub physics_states: ReadStorage<'a, PhysicsState>, // maybe
|
||||
pub pos: ReadStorage<'a, Pos>,
|
||||
|
||||
pub event_bus: Read<'a, EventBus<ServerEvent>>,
|
||||
pub entities_died_last_tick: Read<'a, EntitiesDiedLastTick>,
|
||||
pub block_change: Write<'a, BlockChange>,
|
||||
pub struct ReadData<'a> {
|
||||
entities: Entities<'a>,
|
||||
circuits: ReadStorage<'a, Circuit>,
|
||||
pos: ReadStorage<'a, Pos>,
|
||||
physics_states: ReadStorage<'a, PhysicsState>,
|
||||
entities_died_last_tick: Read<'a, EntitiesDiedLastTick>,
|
||||
}
|
||||
|
||||
/// This system is responsible for handling wiring (signals and wiring systems)
|
||||
#[derive(Default)]
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
type SystemData = WiringData<'a>;
|
||||
type SystemData = (
|
||||
ReadData<'a>,
|
||||
Read<'a, EventBus<ServerEvent>>,
|
||||
WriteStorage<'a, WiringElement>,
|
||||
WriteStorage<'a, LightEmitter>, // maybe
|
||||
Write<'a, BlockChange>,
|
||||
);
|
||||
|
||||
const NAME: &'static str = "wiring";
|
||||
const ORIGIN: Origin = Origin::Server;
|
||||
const PHASE: Phase = Phase::Create;
|
||||
|
||||
fn run(_job: &mut Job<Self>, mut system_data: Self::SystemData) {
|
||||
// Calculate new outputs using inputs (those inputs are calculated and populated
|
||||
// in previous tick) Take inputs and wiring_element.outputs and with
|
||||
// that compute new outputs
|
||||
let computed_outputs = compute_outputs(&system_data);
|
||||
// Pass new outputs as inputs for the next tick
|
||||
dispatch_circuit_transport(&computed_outputs, &mut system_data);
|
||||
// Using inputs dispatch actions
|
||||
dispatch_actions(&mut system_data);
|
||||
fn run(
|
||||
_job: &mut Job<Self>,
|
||||
(read_data, event_bus, mut wiring_elements, mut light_emitters, mut block_change): Self::SystemData,
|
||||
) {
|
||||
let mut server_emitter = event_bus.emitter();
|
||||
|
||||
// Compute the output for each wiring element by computing
|
||||
// the output for each `OutputFormula` and store each value per output per
|
||||
// entity.
|
||||
let computed_outputs: HashMap<Entity, HashMap<String, f32>> = (
|
||||
&read_data.entities,
|
||||
&wiring_elements,
|
||||
read_data.physics_states.maybe(),
|
||||
read_data.pos.maybe(),
|
||||
)
|
||||
.join()
|
||||
.map(|(entity, wiring_element, physics_state, pos)| {
|
||||
(
|
||||
entity,
|
||||
wiring_element
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|(key, output_formula)| {
|
||||
(
|
||||
// Output name/key
|
||||
key.to_string(),
|
||||
// Output value
|
||||
output_formula.compute_output(
|
||||
&wiring_element.inputs,
|
||||
physics_state,
|
||||
&read_data.entities_died_last_tick.0,
|
||||
pos,
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Pass new outputs as inputs for the next tick to the proper elements in the
|
||||
// circuit.
|
||||
(read_data.circuits)
|
||||
.join()
|
||||
.flat_map(|circuit| circuit.wires.iter())
|
||||
.for_each(|wire| {
|
||||
// The current output values becomes input values for the next tick
|
||||
let input_value = computed_outputs
|
||||
.get(&wire.input.entity)
|
||||
.and_then(|e| e.get(&wire.input.name))
|
||||
.unwrap_or(&0.0);
|
||||
|
||||
// Push the current output value into the inputs for the proper element to be
|
||||
// used next tick.
|
||||
if let Some(wiring_element) = wiring_elements.get_mut(wire.output.entity) {
|
||||
wiring_element
|
||||
.inputs
|
||||
.insert(wire.output.name.clone(), *input_value);
|
||||
}
|
||||
});
|
||||
|
||||
// Use inputs to dispatch actions and apply effects
|
||||
(
|
||||
&read_data.entities,
|
||||
&mut wiring_elements,
|
||||
read_data.physics_states.maybe(),
|
||||
(&mut light_emitters).maybe(),
|
||||
read_data.pos.maybe(),
|
||||
)
|
||||
.join()
|
||||
.for_each(
|
||||
|(entity, wiring_element, physics_state, mut light_emitter, pos)| {
|
||||
wiring_element
|
||||
.actions
|
||||
.iter()
|
||||
.filter(|wiring_action| {
|
||||
// Filter out any wiring actions with a total output less than the
|
||||
// threshold
|
||||
wiring_action.formula.compute_output(
|
||||
&wiring_element.inputs,
|
||||
physics_state,
|
||||
&read_data.entities_died_last_tick.0,
|
||||
pos,
|
||||
) >= wiring_action.threshold
|
||||
})
|
||||
.for_each(|wiring_action| {
|
||||
// Apply world effects of each wiring action
|
||||
wiring_action.apply_effects(
|
||||
entity,
|
||||
&wiring_element.inputs,
|
||||
physics_state,
|
||||
&read_data.entities_died_last_tick.0,
|
||||
&mut server_emitter,
|
||||
pos,
|
||||
&mut block_change,
|
||||
light_emitter.as_deref_mut(),
|
||||
);
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_circuit_transport<'a>(
|
||||
computed_outputs: &HashMap<Entity, HashMap<String, f32>>,
|
||||
system_data: &mut WiringData<'a>,
|
||||
) {
|
||||
let WiringData {
|
||||
circuits,
|
||||
wiring_elements,
|
||||
..
|
||||
} = system_data;
|
||||
|
||||
(circuits)
|
||||
.join()
|
||||
.flat_map(|circuit| circuit.wires.iter())
|
||||
.for_each(|wire| {
|
||||
let input_value = computed_outputs
|
||||
.get(&wire.input_entity)
|
||||
.and_then(|e| e.get(&wire.input_field))
|
||||
.unwrap_or(&0.0);
|
||||
|
||||
if let Some(wiring_element) = wiring_elements.get_mut(wire.output_entity) {
|
||||
wiring_element
|
||||
.inputs
|
||||
.insert(wire.output_field.clone(), *input_value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,159 +0,0 @@
|
||||
use super::WiringData;
|
||||
use crate::wiring::{Logic, LogicKind, OutputFormula};
|
||||
use common::{
|
||||
comp::{PhysicsState, Pos},
|
||||
resources::EntitiesDiedLastTick,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use rand_distr::num_traits::ToPrimitive;
|
||||
use specs::{join::Join, Entity, Read};
|
||||
use tracing::warn;
|
||||
|
||||
pub fn compute_outputs(system_data: &WiringData) -> HashMap<Entity, HashMap<String, f32>> {
|
||||
let WiringData {
|
||||
entities,
|
||||
wiring_elements,
|
||||
physics_states,
|
||||
entities_died_last_tick,
|
||||
pos,
|
||||
..
|
||||
} = system_data;
|
||||
(
|
||||
&*entities,
|
||||
wiring_elements,
|
||||
physics_states.maybe(),
|
||||
pos.maybe(),
|
||||
)
|
||||
.join()
|
||||
.map(|(entity, wiring_element, physics_state, pos)| {
|
||||
(
|
||||
entity,
|
||||
wiring_element
|
||||
.outputs
|
||||
.iter()
|
||||
.map(
|
||||
|(key, output_formula)| {
|
||||
compute_output_with_key(
|
||||
key,
|
||||
output_formula,
|
||||
&wiring_element.inputs,
|
||||
physics_state,
|
||||
entities_died_last_tick,
|
||||
pos,
|
||||
)
|
||||
}, // (String, f32)
|
||||
)
|
||||
.collect::<HashMap<_, _>>(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn compute_output_with_key(
|
||||
// yes, this function is defined only to make one place
|
||||
// look a bit nicer
|
||||
// Don't discuss.
|
||||
key: &str,
|
||||
output_formula: &OutputFormula,
|
||||
inputs: &HashMap<String, f32>,
|
||||
physics_state: Option<&PhysicsState>,
|
||||
entities_died_last_tick: &Read<EntitiesDiedLastTick>,
|
||||
pos: Option<&Pos>,
|
||||
) -> (String, f32) {
|
||||
(
|
||||
key.to_string(),
|
||||
compute_output(
|
||||
output_formula,
|
||||
inputs,
|
||||
physics_state,
|
||||
entities_died_last_tick,
|
||||
pos,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn compute_output(
|
||||
output_formula: &OutputFormula,
|
||||
inputs: &HashMap<String, f32>,
|
||||
physics_state: Option<&PhysicsState>,
|
||||
entities_died_last_tick: &Read<EntitiesDiedLastTick>,
|
||||
pos: Option<&Pos>,
|
||||
) -> f32 {
|
||||
match output_formula {
|
||||
OutputFormula::Constant { value } => *value,
|
||||
OutputFormula::Input { name } => *inputs.get(name).unwrap_or(&0.0),
|
||||
OutputFormula::Logic(logic) => {
|
||||
output_formula_logic(logic, inputs, physics_state, entities_died_last_tick, pos)
|
||||
},
|
||||
OutputFormula::SineWave { .. } => {
|
||||
warn!("Not implemented OutputFormula::SineWave");
|
||||
0.0
|
||||
},
|
||||
OutputFormula::OnCollide { value } => output_formula_on_collide(value, physics_state),
|
||||
OutputFormula::OnInteract { .. } => {
|
||||
warn!("Not implemented OutputFormula::OnInteract");
|
||||
0.0
|
||||
},
|
||||
OutputFormula::OnDeath { value, radius } => {
|
||||
output_formula_on_death(value, radius, entities_died_last_tick, pos)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn output_formula_on_collide(value: &f32, physics_state: Option<&PhysicsState>) -> f32 {
|
||||
if let Some(ps) = physics_state {
|
||||
if !ps.touch_entities.is_empty() {
|
||||
return *value;
|
||||
}
|
||||
}
|
||||
0.0
|
||||
}
|
||||
|
||||
fn output_formula_on_death(
|
||||
value: &f32,
|
||||
radius: &f32,
|
||||
entities_died_last_tick: &Read<EntitiesDiedLastTick>,
|
||||
pos: Option<&Pos>,
|
||||
) -> f32 {
|
||||
if let Some(pos_of_entity) = pos {
|
||||
return *value
|
||||
* entities_died_last_tick
|
||||
.0
|
||||
.iter()
|
||||
.filter(|(_, dead_pos)| pos_of_entity.0.distance(dead_pos.0) <= *radius)
|
||||
.count()
|
||||
.to_f32()
|
||||
.unwrap_or(0.0);
|
||||
}
|
||||
0.0
|
||||
}
|
||||
|
||||
fn output_formula_logic(
|
||||
logic: &Logic,
|
||||
inputs: &HashMap<String, f32>,
|
||||
physics_state: Option<&PhysicsState>,
|
||||
entities_died_last_tick: &Read<EntitiesDiedLastTick>,
|
||||
pos: Option<&Pos>,
|
||||
) -> f32 {
|
||||
let left = compute_output(
|
||||
&logic.left,
|
||||
inputs,
|
||||
physics_state,
|
||||
entities_died_last_tick,
|
||||
pos,
|
||||
);
|
||||
let right = compute_output(
|
||||
&logic.right,
|
||||
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,
|
||||
}
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
use std::ops::DerefMut;
|
||||
|
||||
use super::{compute_outputs::compute_output, WiringData};
|
||||
use crate::wiring::{OutputFormula, WiringActionEffect};
|
||||
use common::{
|
||||
comp::{object, Body, LightEmitter, PhysicsState, Pos, ProjectileConstructor},
|
||||
event::{Emitter, ServerEvent},
|
||||
resources::EntitiesDiedLastTick,
|
||||
terrain::{block::Block, TerrainChunkSize},
|
||||
util::Dir,
|
||||
vol::RectVolSize,
|
||||
};
|
||||
use common_state::BlockChange;
|
||||
use hashbrown::HashMap;
|
||||
use specs::{join::Join, Entity, Read, Write};
|
||||
use vek::{Rgb, Vec3};
|
||||
|
||||
pub fn dispatch_actions(system_data: &mut WiringData) {
|
||||
let WiringData {
|
||||
entities,
|
||||
event_bus,
|
||||
wiring_elements,
|
||||
physics_states,
|
||||
light_emitters,
|
||||
entities_died_last_tick,
|
||||
block_change,
|
||||
pos,
|
||||
..
|
||||
} = system_data;
|
||||
let mut server_emitter = event_bus.emitter();
|
||||
|
||||
(
|
||||
&*entities,
|
||||
wiring_elements,
|
||||
physics_states.maybe(),
|
||||
light_emitters.maybe(),
|
||||
pos.maybe(),
|
||||
)
|
||||
.join()
|
||||
.for_each(
|
||||
|(entity, wiring_element, physics_state, mut light_emitter, pos)| {
|
||||
wiring_element
|
||||
.actions
|
||||
.iter()
|
||||
.filter(|wiring_action| {
|
||||
compute_output(
|
||||
&wiring_action.formula,
|
||||
&wiring_element.inputs,
|
||||
physics_state,
|
||||
entities_died_last_tick,
|
||||
pos,
|
||||
) >= wiring_action.threshold
|
||||
})
|
||||
.for_each(|wiring_action| {
|
||||
dispatch_action(
|
||||
entity,
|
||||
&wiring_element.inputs,
|
||||
&wiring_action.effects,
|
||||
&mut server_emitter,
|
||||
&mut light_emitter,
|
||||
physics_state,
|
||||
entities_died_last_tick,
|
||||
block_change,
|
||||
pos,
|
||||
);
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn dispatch_action(
|
||||
entity: Entity,
|
||||
inputs: &HashMap<String, f32>,
|
||||
action_effects: &[WiringActionEffect],
|
||||
|
||||
server_emitter: &mut Emitter<ServerEvent>,
|
||||
|
||||
light_emitter: &mut Option<impl DerefMut<Target = LightEmitter>>,
|
||||
physics_state: Option<&PhysicsState>,
|
||||
entities_died_last_tick: &Read<EntitiesDiedLastTick>,
|
||||
block_change: &mut Write<BlockChange>,
|
||||
pos: Option<&Pos>,
|
||||
) {
|
||||
action_effects
|
||||
.iter()
|
||||
.for_each(|action_effect| match action_effect {
|
||||
WiringActionEffect::SetBlock { coords, block } => {
|
||||
dispatch_action_set_block(*coords, *block, block_change, pos);
|
||||
},
|
||||
WiringActionEffect::SpawnProjectile { constr } => {
|
||||
dispatch_action_spawn_projectile(entity, constr, server_emitter)
|
||||
},
|
||||
WiringActionEffect::SetLight { r, g, b } => dispatch_action_set_light(
|
||||
inputs,
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
&mut light_emitter.as_deref_mut(),
|
||||
physics_state,
|
||||
entities_died_last_tick,
|
||||
pos,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
fn dispatch_action_spawn_projectile(
|
||||
entity: Entity,
|
||||
constr: &ProjectileConstructor,
|
||||
server_emitter: &mut Emitter<ServerEvent>,
|
||||
) {
|
||||
// Use match here if there will be more options
|
||||
// NOTE: constr in RFC is about Arrow projectile
|
||||
server_emitter.emit(ServerEvent::Shoot {
|
||||
entity,
|
||||
pos: Pos(Vec3::zero()),
|
||||
dir: Dir::forward(),
|
||||
body: Body::Object(object::Body::Arrow),
|
||||
projectile: constr.create_projectile(None, 0.0, 1.0, 1.0),
|
||||
light: None,
|
||||
speed: 5.0,
|
||||
object: None,
|
||||
});
|
||||
}
|
||||
|
||||
fn dispatch_action_set_light(
|
||||
inputs: &HashMap<String, f32>,
|
||||
r: &OutputFormula,
|
||||
g: &OutputFormula,
|
||||
b: &OutputFormula,
|
||||
|
||||
light_emitter: &mut Option<&mut LightEmitter>,
|
||||
|
||||
physics_state: Option<&PhysicsState>,
|
||||
entities_died_last_tick: &Read<EntitiesDiedLastTick>,
|
||||
pos: Option<&Pos>,
|
||||
) {
|
||||
if let Some(light_emitter) = light_emitter {
|
||||
// TODO: make compute_output accept multiple formulas
|
||||
|
||||
let computed_r = compute_output(r, inputs, physics_state, entities_died_last_tick, pos);
|
||||
let computed_g = compute_output(g, inputs, physics_state, entities_died_last_tick, pos);
|
||||
let computed_b = compute_output(b, inputs, physics_state, entities_died_last_tick, pos);
|
||||
|
||||
light_emitter.col = Rgb::new(computed_r, computed_g, computed_b);
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_action_set_block(
|
||||
coord: vek::Vec3<i32>,
|
||||
block: Block,
|
||||
block_change: &mut Write<BlockChange>,
|
||||
pos: Option<&Pos>,
|
||||
) {
|
||||
let chunk_origin = pos
|
||||
.map(|opos| {
|
||||
opos.0
|
||||
.xy()
|
||||
.as_::<i32>()
|
||||
.map2(TerrainChunkSize::RECT_SIZE.as_::<i32>(), |a, b| (a / b) * b)
|
||||
.with_z(0)
|
||||
})
|
||||
.unwrap_or_else(vek::Vec3::zero);
|
||||
let offset_pos = chunk_origin + coord;
|
||||
block_change.set(offset_pos, block);
|
||||
}
|
@ -1,58 +1,224 @@
|
||||
use common::{comp::ProjectileConstructor, terrain::Block};
|
||||
use common::{
|
||||
comp::{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, Entity};
|
||||
use specs_idvs::IdvStorage;
|
||||
use vek::Vec3;
|
||||
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>,
|
||||
}
|
||||
|
||||
pub enum OutputFormula {
|
||||
Constant { value: f32 },
|
||||
Input { name: String },
|
||||
Logic(Box<Logic>),
|
||||
|
||||
SineWave { amplitude: f32, frequency: f32 },
|
||||
OnCollide { value: f32 },
|
||||
OnInteract { value: f32 },
|
||||
OnDeath { value: f32, radius: f32 },
|
||||
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 {
|
||||
Min, // acts like And
|
||||
Max, // acts like Or
|
||||
Sub, // `|x| { 5.0 - x }` acts like Not, depending on reference voltages
|
||||
/// 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)>,
|
||||
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_::<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 {
|
||||
server_emitter.emit(ServerEvent::Shoot {
|
||||
entity,
|
||||
pos,
|
||||
dir: Dir::forward(),
|
||||
body: Body::Object(object::Body::Arrow),
|
||||
projectile: constr.create_projectile(None, 0.0, 1.0, 1.0),
|
||||
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 {
|
||||
SpawnProjectile {
|
||||
constr: ProjectileConstructor,
|
||||
},
|
||||
SetBlock {
|
||||
coords: Vec3<i32>,
|
||||
block: Block,
|
||||
},
|
||||
/// 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,
|
||||
@ -60,11 +226,20 @@ pub enum WiringActionEffect {
|
||||
},
|
||||
}
|
||||
|
||||
/// Holds an input and output node.
|
||||
pub struct Wire {
|
||||
pub input_field: String,
|
||||
pub output_field: String,
|
||||
pub input_entity: Entity,
|
||||
pub output_entity: Entity,
|
||||
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 {
|
||||
|
Loading…
Reference in New Issue
Block a user