use bevy::{ecs::message::MessageWriter, prelude::*};
use issun_core::mechanics::evolution::{
EvolutionConfig, EvolutionEvent, SubjectType as CoreSubjectType,
};
use issun_core::mechanics::EventEmitter;
#[derive(Resource, Clone, Reflect, Default)]
#[reflect(Resource)]
pub struct EvolutionConfigResource {
#[reflect(ignore)]
pub config: EvolutionConfig,
}
impl EvolutionConfigResource {
pub fn new(config: EvolutionConfig) -> Self {
Self { config }
}
}
#[derive(Component, Reflect, Clone)]
#[reflect(Component)]
pub struct EvolutionStateComponent {
pub value: f32,
pub min: f32,
pub max: f32,
pub rate_multiplier: f32,
pub subject: SubjectType,
pub status: EvolutionStatus,
}
impl EvolutionStateComponent {
pub fn new(initial_value: f32, min: f32, max: f32, subject: SubjectType) -> Self {
Self {
value: initial_value.clamp(min, max),
min,
max,
rate_multiplier: 1.0,
subject,
status: EvolutionStatus::Active,
}
}
pub fn to_evolution_state(&self) -> issun_core::mechanics::evolution::EvolutionState {
issun_core::mechanics::evolution::EvolutionState {
value: self.value,
min: self.min,
max: self.max,
rate_multiplier: self.rate_multiplier,
subject: self.subject.to_core_subject_type(),
status: self.status.to_core_status(),
}
}
pub fn from_evolution_state(
&mut self,
state: &issun_core::mechanics::evolution::EvolutionState,
) {
self.value = state.value;
self.min = state.min;
self.max = state.max;
self.rate_multiplier = state.rate_multiplier;
self.subject = SubjectType::from_core_subject_type(state.subject);
self.status = EvolutionStatus::from_core_status(state.status);
}
pub fn is_at_min(&self) -> bool {
(self.value - self.min).abs() < f32::EPSILON
}
pub fn is_at_max(&self) -> bool {
(self.value - self.max).abs() < f32::EPSILON
}
pub fn normalized(&self) -> f32 {
if (self.max - self.min).abs() < f32::EPSILON {
0.0
} else {
(self.value - self.min) / (self.max - self.min)
}
}
pub fn is_active(&self) -> bool {
matches!(self.status, EvolutionStatus::Active)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect)]
pub enum SubjectType {
Food,
Plant,
Resource,
Equipment,
Population,
Custom(u32),
}
impl SubjectType {
fn to_core_subject_type(self) -> CoreSubjectType {
match self {
SubjectType::Food => CoreSubjectType::Food,
SubjectType::Plant => CoreSubjectType::Plant,
SubjectType::Resource => CoreSubjectType::Resource,
SubjectType::Equipment => CoreSubjectType::Equipment,
SubjectType::Population => CoreSubjectType::Population,
SubjectType::Custom(id) => CoreSubjectType::Custom(id),
}
}
fn from_core_subject_type(core: CoreSubjectType) -> Self {
match core {
CoreSubjectType::Food => SubjectType::Food,
CoreSubjectType::Plant => SubjectType::Plant,
CoreSubjectType::Resource => SubjectType::Resource,
CoreSubjectType::Equipment => SubjectType::Equipment,
CoreSubjectType::Population => SubjectType::Population,
CoreSubjectType::Custom(id) => SubjectType::Custom(id),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect)]
pub enum EvolutionStatus {
Active,
Paused,
Completed,
Depleted,
}
impl EvolutionStatus {
fn to_core_status(self) -> issun_core::mechanics::evolution::EvolutionStatus {
match self {
EvolutionStatus::Active => issun_core::mechanics::evolution::EvolutionStatus::Active,
EvolutionStatus::Paused => issun_core::mechanics::evolution::EvolutionStatus::Paused,
EvolutionStatus::Completed => {
issun_core::mechanics::evolution::EvolutionStatus::Completed
}
EvolutionStatus::Depleted => {
issun_core::mechanics::evolution::EvolutionStatus::Depleted
}
}
}
fn from_core_status(core: issun_core::mechanics::evolution::EvolutionStatus) -> Self {
match core {
issun_core::mechanics::evolution::EvolutionStatus::Active => EvolutionStatus::Active,
issun_core::mechanics::evolution::EvolutionStatus::Paused => EvolutionStatus::Paused,
issun_core::mechanics::evolution::EvolutionStatus::Completed => {
EvolutionStatus::Completed
}
issun_core::mechanics::evolution::EvolutionStatus::Depleted => {
EvolutionStatus::Depleted
}
}
}
}
#[derive(Debug, Component, Clone, Reflect)]
#[reflect(Component)]
pub struct EnvironmentComponent {
pub temperature: f32,
pub humidity: f32,
pub pressure: f32,
}
impl Default for EnvironmentComponent {
fn default() -> Self {
Self {
temperature: 20.0,
humidity: 0.5,
pressure: 1.0,
}
}
}
impl EnvironmentComponent {
pub fn new(temperature: f32, humidity: f32) -> Self {
Self {
temperature,
humidity,
pressure: 1.0,
}
}
pub fn to_core_environment(&self) -> issun_core::mechanics::evolution::Environment {
issun_core::mechanics::evolution::Environment {
temperature: self.temperature,
humidity: self.humidity,
pressure: self.pressure,
custom: std::collections::HashMap::new(),
}
}
}
#[derive(bevy::ecs::message::Message, Clone, Debug, PartialEq)]
pub struct EvolutionEventWrapper {
pub entity: Entity,
pub event: EvolutionEvent,
}
pub struct BevyEventEmitter<'a, 'b> {
entity: Entity,
writer: &'a mut MessageWriter<'b, EvolutionEventWrapper>,
}
impl<'a, 'b> BevyEventEmitter<'a, 'b> {
pub fn new(entity: Entity, writer: &'a mut MessageWriter<'b, EvolutionEventWrapper>) -> Self {
Self { entity, writer }
}
}
impl<'a, 'b> EventEmitter<EvolutionEvent> for BevyEventEmitter<'a, 'b> {
fn emit(&mut self, event: EvolutionEvent) {
self.writer.write(EvolutionEventWrapper {
entity: self.entity,
event,
});
}
}
#[derive(bevy::ecs::message::Message, Clone, Debug)]
pub struct EvolutionTick {
pub entity: Entity,
pub time_delta: Option<f32>,
pub custom_environment: Option<EnvironmentComponent>,
}
impl EvolutionTick {
pub fn new(entity: Entity) -> Self {
Self {
entity,
time_delta: None,
custom_environment: None,
}
}
pub fn with_time_delta(entity: Entity, time_delta: f32) -> Self {
Self {
entity,
time_delta: Some(time_delta),
custom_environment: None,
}
}
pub fn with_environment(entity: Entity, environment: EnvironmentComponent) -> Self {
Self {
entity,
time_delta: None,
custom_environment: Some(environment),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_evolution_state_component() {
let state = EvolutionStateComponent::new(50.0, 0.0, 100.0, SubjectType::Food);
assert_eq!(state.value, 50.0);
assert_eq!(state.min, 0.0);
assert_eq!(state.max, 100.0);
assert!(state.is_active());
}
#[test]
fn test_evolution_state_to_core() {
let bevy_state = EvolutionStateComponent::new(75.0, 0.0, 100.0, SubjectType::Plant);
let core_state = bevy_state.to_evolution_state();
assert_eq!(core_state.value, 75.0);
}
#[test]
fn test_evolution_state_from_core() {
let mut bevy_state = EvolutionStateComponent::new(50.0, 0.0, 100.0, SubjectType::Food);
let core_state = issun_core::mechanics::evolution::EvolutionState {
value: 25.0,
min: 0.0,
max: 100.0,
rate_multiplier: 1.0,
subject: CoreSubjectType::Food,
status: issun_core::mechanics::evolution::EvolutionStatus::Active,
};
bevy_state.from_evolution_state(&core_state);
assert_eq!(bevy_state.value, 25.0);
}
#[test]
fn test_environment_component() {
let env = EnvironmentComponent::new(25.0, 0.7);
assert_eq!(env.temperature, 25.0);
assert_eq!(env.humidity, 0.7);
}
#[test]
fn test_evolution_tick() {
let entity = Entity::PLACEHOLDER;
let tick = EvolutionTick::new(entity);
assert_eq!(tick.entity, entity);
assert!(tick.time_delta.is_none());
}
}