use arrayvec::ArrayVec;
use log::trace;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{
config::TCMB,
effects::{GasReactionEffectSerialized, dispatch_reaction_effect},
gases::{Gas, GasMixture, Moles, MolesWrapper, gases},
};
const REACTION_BUFFER_SIZE: usize = 18;
pub(crate) const ATMOS_TICKRATE: f32 = 0.5;
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ReactionBuffer {
#[cfg_attr(feature = "serde", serde(rename = "reactions"))]
Protos(ArrayVec<GasReactionPrototype, REACTION_BUFFER_SIZE>),
#[cfg_attr(feature = "serde", serde(skip))]
Inited(ArrayVec<GasReaction, REACTION_BUFFER_SIZE>),
}
impl Default for GasReactionPrototype {
fn default() -> Self {
Self {
priority: 0,
minimum_temperature: TCMB,
maximum_temperature: f32::MAX,
minimum_requirements: MolesWrapper::default(),
minimum_energy: 0.,
effect: GasReactionEffectSerialized::default(),
}
}
}
impl GasReactionPrototype {
#[must_use]
pub fn new(effect: GasReactionEffectSerialized) -> Self {
Self {
effect,
..Default::default()
}
}
#[must_use]
pub fn plasma_fire() -> Self {
Self {
priority: -2,
minimum_temperature: 373.149,
minimum_requirements: MolesWrapper(gases! {
Gas::Oxygen => 0.01,
Gas::Plasma => 0.01,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::PlasmaFire)
}
}
#[must_use]
pub fn tritium_fire() -> Self {
Self {
priority: -1,
minimum_temperature: 373.149,
minimum_requirements: MolesWrapper(gases! {
Gas::Oxygen => 0.01,
Gas::Tritium => 0.01,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::TritiumFire)
}
}
#[must_use]
pub fn frezon_coolant() -> Self {
Self {
priority: 1,
minimum_temperature: 23.15,
minimum_requirements: MolesWrapper(gases! {
Gas::Nitrogen => 0.01,
Gas::Frezon => 0.01,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::FrezonCoolant)
}
}
#[must_use]
pub fn frezon_production() -> Self {
Self {
priority: 2,
maximum_temperature: 73.15,
minimum_requirements: MolesWrapper(gases! {
Gas::Oxygen => 0.01,
Gas::Nitrogen => 0.01,
Gas::Tritium => 0.01,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::FrezonProduction)
}
}
#[must_use]
pub fn ammonia_oxygen() -> Self {
Self {
priority: 2,
minimum_temperature: 323.149,
minimum_requirements: MolesWrapper(gases! {
Gas::Oxygen => 0.01,
Gas::Ammonia => 0.01,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::AmmoniaOxygen)
}
}
#[must_use]
pub fn n2o_decomposition() -> Self {
Self {
priority: 0,
minimum_temperature: 850.,
minimum_requirements: MolesWrapper(gases! {
Gas::NitrousOxide => 0.01,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::N2ODecomposition)
}
}
#[must_use]
pub fn old_tritium_fire() -> Self {
Self {
priority: -1,
minimum_temperature: 373.149,
minimum_requirements: MolesWrapper(gases! {
Gas::Oxygen => 0.01,
Gas::Tritium => 0.01,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::TritiumFireOld)
}
}
#[must_use]
pub fn frontier_tritium_fire() -> Self {
Self {
minimum_temperature: 700.,
..Self::old_tritium_fire()
}
}
#[must_use]
pub fn goob_n2o_formation() -> Self {
Self {
priority: 2,
minimum_temperature: 200.,
maximum_temperature: 250., minimum_requirements: MolesWrapper(gases! {
Gas::Oxygen => 0.01,
Gas::Nitrogen => 0.01,
Gas::BZ => 5., _ => 0.,
}),
..Self::new(GasReactionEffectSerialized::N2OFormation)
}
}
#[must_use]
pub fn funky_bz_formation() -> Self {
Self {
priority: 2,
minimum_temperature: 313.149,
minimum_requirements: MolesWrapper(gases! {
Gas::Plasma => 10.,
Gas::NitrousOxide => 10.,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::BZFormation)
}
}
#[must_use]
pub fn funky_healium_production() -> Self {
Self {
maximum_temperature: 300.,
minimum_temperature: 22.,
..Self::mono_healium_production()
}
}
#[must_use]
pub fn mono_healium_production() -> Self {
Self {
priority: 2,
minimum_requirements: MolesWrapper(gases! {
Gas::Frezon => 5.,
Gas::BZ => 5.,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::HealiumProduction)
}
}
#[must_use]
pub fn funky_nitrium_production() -> Self {
Self {
priority: 2,
minimum_temperature: 1500.,
minimum_requirements: MolesWrapper(gases! {
Gas::Nitrogen => 10.,
Gas::Tritium => 20.,
Gas::BZ => 5.,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::NitriumProduction)
}
}
#[must_use]
pub fn mono_nitrium_production() -> Self {
Self {
minimum_requirements: MolesWrapper(gases! {
Gas::Nitrogen => 20., Gas::Tritium => 20.,
Gas::BZ => 5.,
_ => 0.,
}),
..Self::funky_nitrium_production()
}
}
#[must_use]
pub fn funky_nitrium_decomposition() -> Self {
Self {
priority: 2,
maximum_temperature: 343.15, minimum_requirements: MolesWrapper(gases! {
Gas::Oxygen => 0.01,
Gas::Nitrium => 0.01,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::NitriumDecomposition)
}
}
#[must_use]
pub fn funky_pluoxium_production() -> Self {
Self {
priority: 3,
minimum_temperature: 50.,
maximum_temperature: 273.15,
minimum_requirements: MolesWrapper(gases! {
Gas::Oxygen => 0.01,
Gas::CarbonDioxide => 0.01,
Gas::Tritium => 0.01,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::PluoxiumProduction)
}
}
#[must_use]
pub fn funky_hydrogen_fire() -> Self {
Self {
priority: -1,
minimum_temperature: 373.149,
minimum_requirements: MolesWrapper(gases! {
Gas::Oxygen => 0.01,
Gas::Hydrogen => 0.01,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::HydrogenFire)
}
}
#[must_use]
pub fn funky_hyper_noblium_production() -> Self {
Self {
priority: 7,
minimum_temperature: 2.7,
maximum_temperature: 15.,
minimum_requirements: MolesWrapper(gases! {
Gas::Nitrogen => 10.,
Gas::Tritium => 5.,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::HyperNobliumProduction)
}
}
#[must_use]
pub fn funky_halon_oxygen_absorbtion() -> Self {
Self {
priority: -4,
minimum_temperature: 373.15,
minimum_requirements: MolesWrapper(gases! {
Gas::Oxygen => 0.01,
Gas::Halon => 0.01,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::HalonOxygenAbsorbtion)
}
}
#[must_use]
pub fn funky_zauker_production() -> Self {
Self {
priority: 9,
minimum_temperature: 50000.,
maximum_temperature: 75000.,
minimum_requirements: MolesWrapper(gases! {
Gas::Nitrium => 0.01,
Gas::HyperNoblium => 0.01,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::ZaukerProduction)
}
}
#[must_use]
pub fn funky_zauker_decomposition() -> Self {
Self {
priority: 10,
minimum_requirements: MolesWrapper(gases! {
Gas::Nitrogen => 0.01,
Gas::Zauker => 0.01,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::ZaukerDecomposition)
}
}
#[must_use]
pub fn funky_proto_nitrate_production() -> Self {
Self {
priority: 11,
minimum_temperature: 5000.,
maximum_temperature: 10000.,
minimum_requirements: MolesWrapper(gases! {
Gas::Pluoxium => 0.01,
Gas::Hydrogen => 0.01,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::ProtoNitrateProduction)
}
}
#[must_use]
pub fn funky_proto_nitrate_conversion() -> Self {
Self {
priority: 12,
minimum_requirements: MolesWrapper(gases! {
Gas::ProtoNitrate => 100.,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::ProtoNitrateConversion)
}
}
#[must_use]
pub fn funky_proto_nitrate_bz_conversion() -> Self {
Self {
priority: 13,
minimum_temperature: 260.,
maximum_temperature: 280.,
minimum_requirements: MolesWrapper(gases! {
Gas::BZ => 0.01,
Gas::ProtoNitrate => 0.01,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::ProtoNitrateBZConversion)
}
}
#[must_use]
pub fn klovn_zipion_formation() -> Self {
Self {
priority: 2,
maximum_temperature: 200.,
minimum_requirements: MolesWrapper(gases! {
Gas::Plasma => 1.,
Gas::WaterVapor => 9.,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::ZipionFormation)
}
}
#[must_use]
pub fn klovn_argon_formation() -> Self {
Self {
priority: 2,
maximum_temperature: 589.,
minimum_requirements: MolesWrapper(gases! {
Gas::Frezon => 1.,
Gas::Plasma => 2.,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::ArgonFormation)
}
}
#[must_use]
pub fn klovn_argon_absorbtion() -> Self {
Self {
priority: 3,
minimum_requirements: MolesWrapper(gases! {
Gas::Argon => 80.,
_ => 0.,
}),
..Self::new(GasReactionEffectSerialized::ArgonAbsorbtion)
}
}
}
impl Default for ReactionBuffer {
fn default() -> Self {
Self::Protos(
[
GasReactionPrototype::plasma_fire(),
GasReactionPrototype::tritium_fire(),
GasReactionPrototype::frezon_coolant(),
GasReactionPrototype::frezon_production(),
GasReactionPrototype::ammonia_oxygen(),
GasReactionPrototype::n2o_decomposition(),
]
.into_iter()
.collect(),
)
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct GasReactionPrototype {
pub effect: GasReactionEffectSerialized,
pub priority: i8,
pub minimum_temperature: f32,
pub maximum_temperature: f32,
pub minimum_energy: f32,
pub minimum_requirements: MolesWrapper,
}
pub enum ReactionResult {
NoReaction,
Reacting,
StopReactions,
}
pub type GasReactionEffectSignature = fn(&mut GasMixture) -> ReactionResult;
#[derive(Debug, Clone, Copy)]
pub struct GasReaction {
pub minimum_temperature: f32,
pub maximum_temperature: f32,
pub minimum_requirements: Moles,
pub minimum_energy: f32,
pub priority: i8,
pub effect: GasReactionEffectSignature,
}
impl GasReaction {
pub(crate) fn cache(proto: GasReactionPrototype) -> Self {
Self {
priority: proto.priority,
maximum_temperature: proto.maximum_temperature,
minimum_temperature: proto.minimum_temperature,
minimum_requirements: proto.minimum_requirements.0,
minimum_energy: proto.minimum_energy,
effect: dispatch_reaction_effect(proto.effect),
}
}
}
impl GasMixture<'_> {
pub fn react(&mut self) -> bool {
let mut reacted = false;
let mut previous_priority = None;
if let ReactionBuffer::Inited(reactions) = &self.engine.game.reactions {
for reaction in reactions {
if self.get_thermal_energy() >= reaction.minimum_energy
&& self.temperature() >= reaction.minimum_temperature
&& reaction.maximum_temperature >= self.temperature()
&& reaction
.minimum_requirements
.iter()
.all(|(gas, required)| &self.get_moles(gas) >= required)
{
match (reaction.effect)(self) {
ReactionResult::Reacting => {
reacted = true;
previous_priority = priority_warn(previous_priority, reaction.priority);
}
ReactionResult::NoReaction => (),
ReactionResult::StopReactions => {
reacted = true;
priority_warn(previous_priority, reaction.priority);
break;
}
}
}
}
} else {
unreachable!("Engine should've been inited")
}
reacted
}
}
fn priority_warn(previous: Option<i8>, current: i8) -> Option<i8> {
let current_priority = Some(current);
if previous == current_priority {
trace!(
"Two reactions with the same priority {current_priority:?} ran in a row, the result might be unpredictable"
);
}
current_priority
}