summaryrefslogtreecommitdiff
path: root/src/content
diff options
context:
space:
mode:
Diffstat (limited to 'src/content')
-rw-r--r--src/content/entities/entity_cube.rs30
-rw-r--r--src/content/entities/entity_grenade.rs61
-rw-r--r--src/content/entities/mod.rs2
-rw-r--r--src/content/materials/fire.rs80
-rw-r--r--src/content/materials/gas.rs24
-rw-r--r--src/content/materials/liquid.rs75
-rw-r--r--src/content/materials/mod.rs111
-rw-r--r--src/content/materials/powder.rs10
-rw-r--r--src/content/mod.rs2
9 files changed, 395 insertions, 0 deletions
diff --git a/src/content/entities/entity_cube.rs b/src/content/entities/entity_cube.rs
new file mode 100644
index 0000000..ab93e5e
--- /dev/null
+++ b/src/content/entities/entity_cube.rs
@@ -0,0 +1,30 @@
+use glam::{IVec2, Vec2};
+
+use crate::{
+ content::materials::MaterialId,
+ sim::{
+ cell::cell::Cell,
+ entity::{EntityCells, EntityDef},
+ },
+};
+
+pub fn entity_cube_def(position: Vec2, material: MaterialId) -> EntityDef {
+ let w = 10;
+ let h = 10;
+ let mut raw_cells = vec![Cell::void(); (w * h) as usize];
+
+ for x in 0..w {
+ for y in 0..h {
+ let cell_idx = x + y * w;
+ raw_cells[cell_idx as usize] = Cell::from_material(material);
+ raw_cells[cell_idx as usize].set_entity(true);
+ }
+ }
+
+ let entity_cells = EntityCells {
+ cells: raw_cells,
+ size: IVec2::new(w, h),
+ };
+
+ EntityDef::from_cells(position, entity_cells, None)
+}
diff --git a/src/content/entities/entity_grenade.rs b/src/content/entities/entity_grenade.rs
new file mode 100644
index 0000000..ab7aaa4
--- /dev/null
+++ b/src/content/entities/entity_grenade.rs
@@ -0,0 +1,61 @@
+use glam::{IVec2, Vec2};
+
+use crate::{
+ content::materials::MaterialId,
+ sim::{
+ cell::cell::Cell,
+ entity::{EntityBehaviour, EntityCells, EntityDef, EntityUpdateCtx},
+ lib::force::apply_explosion,
+ sim_manager::SimCtx,
+ },
+};
+
+struct GrenadeEntityBehaviour {
+ pub fuse: f32,
+}
+
+impl EntityBehaviour for GrenadeEntityBehaviour {
+ fn update(
+ &mut self,
+ update_ctx: &mut EntityUpdateCtx,
+ ctx: &mut SimCtx,
+ delta_time: f32,
+ ) -> () {
+ self.fuse -= delta_time;
+ if self.fuse <= 0.0 {
+ apply_explosion(
+ ctx,
+ update_ctx.entity_data.transform(ctx).unwrap().0,
+ 15,
+ Vec2::new(0.0, -0.6),
+ 200.0,
+ );
+ update_ctx.deferred_destroy(update_ctx.entity_data.id);
+ }
+ }
+}
+
+pub fn entity_grenade_def(position: Vec2, fuse: f32) -> EntityDef {
+ let w = 3;
+ let h = 3;
+ let mut raw_cells = vec![Cell::void(); (w * h) as usize];
+
+ for x in 0..w {
+ for y in 0..h {
+ let cell_idx = x + y * w;
+ raw_cells[cell_idx as usize] = Cell::from_material(MaterialId::Steel);
+ raw_cells[cell_idx as usize].set_entity(true);
+ }
+ }
+
+ let entity_cells = EntityCells {
+ cells: raw_cells,
+ size: IVec2::new(w, h),
+ };
+
+ EntityDef::from_cells(
+ position,
+ entity_cells,
+ Some(Box::new(GrenadeEntityBehaviour { fuse })),
+ )
+}
diff --git a/src/content/entities/mod.rs b/src/content/entities/mod.rs
new file mode 100644
index 0000000..e78eb89
--- /dev/null
+++ b/src/content/entities/mod.rs
@@ -0,0 +1,2 @@
+pub mod entity_cube;
+pub mod entity_grenade;
diff --git a/src/content/materials/fire.rs b/src/content/materials/fire.rs
new file mode 100644
index 0000000..483afbc
--- /dev/null
+++ b/src/content/materials/fire.rs
@@ -0,0 +1,80 @@
+use rand::RngExt;
+
+use crate::{
+ content::materials::MaterialId,
+ sim::{
+ cell::cell::Cell,
+ cell_manager::sim::{PostUpdateAction, UpdateCtx},
+ },
+};
+
+pub trait FireCellView {
+ fn get_ticks_lived(self) -> u16;
+ fn set_ticks_lived(&mut self, ticks: u16) -> ();
+ fn is_flammable(self) -> bool;
+}
+
+impl FireCellView for Cell {
+ fn get_ticks_lived(self) -> u16 {
+ self.data
+ }
+ fn set_ticks_lived(&mut self, ticks: u16) {
+ self.data = ticks;
+ }
+ // this could be a property of the material def, I think it's better here for now
+ fn is_flammable(self) -> bool {
+ [MaterialId::Wood].contains(&self.material)
+ }
+}
+
+// TODO optimize number of rng calls?
+// TODO wtf is fire
+#[inline]
+pub fn sim_update(ctx: &mut UpdateCtx) -> PostUpdateAction {
+ let ticks_lived = ctx.cell.get_ticks_lived();
+ // 2 seconds
+ if ticks_lived > 240 {
+ // we have a chance to live longer--roughly ?% chance of living an extra second
+ if ctx.rng.random_range(0.0..1.0) > 0.9 {
+ // kill ourselves, with a chance to turn into ash
+ ctx.set_cell(0, 0, Cell::void());
+ // the updater should take no action since we already killed ourselves
+ return PostUpdateAction::None;
+ }
+ }
+
+ // at an average of 4 times per lifespan, try to spread
+ // 1/60 * 240 = 4
+ if ctx.rng.random_range(0.0..1.0) > (59.0 / 60.0) {
+ let (dx, dy) = (ctx.rng.random_range(-1..=1), ctx.rng.random_range(-1..=1));
+ if let Some(target) = ctx.get_cell(dx, dy)
+ && target.is_flammable()
+ {
+ ctx.set_cell(dx, dy, Cell::from_material(MaterialId::Fire));
+ }
+ }
+
+ // 8 times in our lifespan, emit smoke
+ if ticks_lived.is_multiple_of(30)
+ && let Some(target) = ctx.get_cell(0, -1)
+ && target.material == MaterialId::Void
+ {
+ for (dx, dy) in [
+ (0, -1),
+ (1 - ctx.seqno_parity as i32 * 2, 0),
+ (-1 + ctx.seqno_parity as i32 * 2, 0),
+ (0, 1),
+ ] {
+ if let Some(target) = ctx.get_cell(dx, dy)
+ && target.material == MaterialId::Void
+ {
+ ctx.set_cell(dx, dy, Cell::from_material(MaterialId::Smoke));
+ break;
+ }
+ }
+ }
+
+ ctx.cell.set_ticks_lived(ticks_lived + 1);
+ // apply our new lifespan
+ PostUpdateAction::Apply
+}
diff --git a/src/content/materials/gas.rs b/src/content/materials/gas.rs
new file mode 100644
index 0000000..229d0e9
--- /dev/null
+++ b/src/content/materials/gas.rs
@@ -0,0 +1,24 @@
+use rand::RngExt;
+
+use crate::sim::cell_manager::sim::{PostUpdateAction, UpdateCtx};
+
+#[inline]
+pub fn sim_update(ctx: &mut UpdateCtx) -> PostUpdateAction {
+ // only allow upward movement some of the time to limit movement speed
+ if ctx.rng.random_range(0.0..1.0) > 0.8
+ && ctx.swap_or_settle(&[(0, -1)]) == PostUpdateAction::None
+ {
+ return PostUpdateAction::None;
+ }
+ // same for each horizontal direction
+ if ctx.rng.random_range(0.0..1.0) > 0.8
+ && ctx.swap_or_settle(&[(1 - ctx.seqno_parity as i32 * 2, 0)]) == PostUpdateAction::None
+ {
+ return PostUpdateAction::None;
+ }
+ if ctx.rng.random_range(0.0..1.0) > 0.8 {
+ return ctx.swap_or_settle(&[(-1 + ctx.seqno_parity as i32 * 2, 0)]);
+ }
+
+ PostUpdateAction::Apply
+}
diff --git a/src/content/materials/liquid.rs b/src/content/materials/liquid.rs
new file mode 100644
index 0000000..6c3bdcd
--- /dev/null
+++ b/src/content/materials/liquid.rs
@@ -0,0 +1,75 @@
+use crate::sim::cell_manager::sim::{PostUpdateAction, UpdateCtx};
+
+#[inline]
+pub fn sim_update(ctx: &mut UpdateCtx) -> PostUpdateAction {
+ // if the water can fall, do so
+ if ctx.swap_or_settle(&[
+ (0, 1),
+ (-1 + 2 * ctx.seqno_parity as i32, 1),
+ (1 - 2 * ctx.seqno_parity as i32, 1),
+ ]) == PostUpdateAction::None
+ {
+ return PostUpdateAction::None;
+ }
+
+ // if the water can't fall, check if we can move left or right
+ // these are inverted on parity so that we don't preference a direction
+ let left_target = ctx.get_cell(-1, 0);
+ let can_move_left =
+ left_target.is_some_and(|c| c.material.def().density < ctx.material.density);
+ let right_target = ctx.get_cell(1, 0);
+ let can_move_right =
+ right_target.is_some_and(|c| c.material.def().density < ctx.material.density);
+
+ // we can't move down or to other side, so we're stuck
+ if !can_move_left && !can_move_right {
+ return PostUpdateAction::Settle;
+ }
+
+ // if we can't move left, just move right
+ if !can_move_left {
+ return ctx.swap_or_settle(&[(1, 0)]);
+ }
+ // and vice versa
+ if !can_move_right {
+ return ctx.swap_or_settle(&[(-1, 0)]);
+ }
+
+ // find the closest hole within 20 pixels (TODO optimize)
+ // a hole is any space below us with a lesser density
+ // prevents equidistance stuck state
+ let starting_side = if ctx.seqno_parity == 0 { 1 } else { -1 };
+ for i in 0..20 {
+ let side = if i % 2 == 0 {
+ starting_side
+ } else {
+ -starting_side
+ };
+
+ let offset = side * (1 + i / 2);
+ let hole_target = ctx.get_cell(offset, 1);
+ if let Some(target) = hole_target
+ && target.material.def().density < ctx.material.density
+ {
+ // we identified a hole and we know that the space on this side is open
+ // move toward the hole
+ return ctx.swap_or_settle(&[(side, 0)]);
+ }
+ }
+
+ return PostUpdateAction::Settle;
+
+ // we didn't find a hole, so just move "randomly" on the same surface
+ // TODO when to settle?
+ // let target_x = if !can_move_left {
+ // 1
+ // } else if !can_move_right {
+ // -1
+ // } else if ctx.seqno_parity % 2 == 1 {
+ // 1
+ // } else {
+ // -1
+ // };
+
+ // ctx.candidates_swap(&[(target_x, 0)]);
+}
diff --git a/src/content/materials/mod.rs b/src/content/materials/mod.rs
new file mode 100644
index 0000000..8116565
--- /dev/null
+++ b/src/content/materials/mod.rs
@@ -0,0 +1,111 @@
+use crate::sim::cell_manager::sim::{PostUpdateAction, UpdateCtx};
+
+pub mod fire;
+pub mod gas;
+pub mod liquid;
+pub mod powder;
+
+#[repr(u8)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum MaterialId {
+ Void = 0,
+ Sand,
+ Wood,
+ Water,
+ Fire,
+ Smoke,
+ Steel,
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum MaterialForm {
+ Void,
+ Gas,
+ Liquid,
+ Powder,
+ Solid,
+}
+
+pub struct MaterialDef {
+ pub name: &'static str,
+ pub color: (u8, u8, u8, u8),
+ pub density: u8,
+ pub form: MaterialForm,
+ pub sim_update: Option<fn(ctx: &mut UpdateCtx) -> PostUpdateAction>,
+}
+
+static MATERIALS: [MaterialDef; 7] = [
+ MaterialDef {
+ name: "Void",
+ color: (0x00, 0x00, 0x00, 0x00),
+ density: 0,
+ // form usually but not always determines the update fn
+ form: MaterialForm::Void,
+ sim_update: None,
+ },
+ MaterialDef {
+ name: "Sand",
+ color: (0xDE, 0xCB, 0x85, 0xFF),
+ density: 50,
+ form: MaterialForm::Powder,
+ sim_update: Some(powder::sim_update),
+ },
+ MaterialDef {
+ name: "Wood",
+ color: (0x85, 0x56, 0x1D, 0xFF),
+ density: 50,
+ form: MaterialForm::Solid,
+ sim_update: None,
+ },
+ MaterialDef {
+ name: "Water",
+ color: (0x38, 0xA9, 0xFF, 0xFF),
+ density: 40,
+ form: MaterialForm::Liquid,
+ sim_update: Some(liquid::sim_update),
+ },
+ // TODO this is not a material
+ MaterialDef {
+ name: "Fire",
+ color: (0xFC, 0x66, 0x00, 0xAA),
+ // for now this matches wood
+ density: 50,
+ form: MaterialForm::Gas,
+ sim_update: Some(fire::sim_update),
+ },
+ MaterialDef {
+ name: "Smoke",
+ color: (0x32, 0x35, 0x36, 0xAA),
+ density: 15,
+ form: MaterialForm::Gas,
+ sim_update: Some(gas::sim_update),
+ },
+ MaterialDef {
+ name: "Steel",
+ color: (0x60, 0x60, 0x60, 0xFF),
+ density: 150,
+ form: MaterialForm::Solid,
+ sim_update: None,
+ },
+];
+
+impl MaterialId {
+ pub const ALL: [MaterialId; 7] = [
+ MaterialId::Void,
+ MaterialId::Sand,
+ MaterialId::Wood,
+ MaterialId::Water,
+ MaterialId::Fire,
+ MaterialId::Smoke,
+ MaterialId::Steel,
+ ];
+ #[inline]
+ pub fn def(self) -> &'static MaterialDef {
+ &MATERIALS[self as usize]
+ }
+
+ pub fn from_index(idx: u8) -> MaterialId {
+ MaterialId::ALL[idx as usize]
+ }
+}
diff --git a/src/content/materials/powder.rs b/src/content/materials/powder.rs
new file mode 100644
index 0000000..8c916e4
--- /dev/null
+++ b/src/content/materials/powder.rs
@@ -0,0 +1,10 @@
+use crate::sim::cell_manager::sim::{PostUpdateAction, UpdateCtx};
+
+#[inline]
+pub fn sim_update(ctx: &mut UpdateCtx) -> PostUpdateAction {
+ ctx.swap_or_settle(&[
+ (0, 1),
+ (-1 + 2 * ctx.seqno_parity as i32, 1),
+ (1 - 2 * ctx.seqno_parity as i32, 1),
+ ])
+}
diff --git a/src/content/mod.rs b/src/content/mod.rs
new file mode 100644
index 0000000..2044048
--- /dev/null
+++ b/src/content/mod.rs
@@ -0,0 +1,2 @@
+pub mod entities;
+pub mod materials;