fmt + move cell::on_click into its own function

This commit is contained in:
Arkitu 2025-05-12 15:15:54 +02:00
parent dbb17f0c48
commit d210af93f8
8 changed files with 337 additions and 207 deletions

View File

@ -2,9 +2,7 @@ use bevy::prelude::*;
pub struct Plugin; pub struct Plugin;
impl bevy::prelude::Plugin for Plugin { impl bevy::prelude::Plugin for Plugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {}
}
} }
#[derive(PartialEq, Eq, Clone, Copy)] #[derive(PartialEq, Eq, Clone, Copy)]
@ -14,5 +12,5 @@ pub enum AnimalKind {
#[derive(Component)] #[derive(Component)]
pub struct Animal { pub struct Animal {
kind: AnimalKind kind: AnimalKind,
} }

View File

@ -1,6 +1,13 @@
use std::time::Duration; use std::time::Duration;
use bevy::{input::mouse::MouseWheel, math::NormedVectorSpace, picking::{focus::HoverMap, pointer::PointerId}, prelude::*, utils::HashMap, window::PrimaryWindow}; use bevy::{
input::mouse::MouseWheel,
math::NormedVectorSpace,
picking::{focus::HoverMap, pointer::PointerId},
prelude::*,
utils::HashMap,
window::PrimaryWindow,
};
use crate::ui; use crate::ui;
@ -8,17 +15,14 @@ pub struct Plugin;
impl bevy::prelude::Plugin for Plugin { impl bevy::prelude::Plugin for Plugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup); app.add_systems(Startup, setup);
// .add_systems(Update, move_cam) // .add_systems(Update, move_cam)
// .init_resource::<Pointers>(); // .init_resource::<Pointers>();
} }
} }
fn setup(mut cmds: Commands, window: Query<&Window>) { fn setup(mut cmds: Commands, window: Query<&Window>) {
let zoom = 2./window.single().width().min(window.single().height()); let zoom = 2. / window.single().width().min(window.single().height());
cmds.spawn(( cmds.spawn((Camera2d, Transform::from_scale(Vec3::new(zoom, zoom, zoom))));
Camera2d,
Transform::from_scale(Vec3::new(zoom, zoom, zoom))
));
} }
#[derive(Resource, Default)] #[derive(Resource, Default)]
@ -33,50 +37,80 @@ fn move_cam(
window: Query<&Window, With<PrimaryWindow>>, window: Query<&Window, With<PrimaryWindow>>,
mut pointers: ResMut<Pointers>, mut pointers: ResMut<Pointers>,
hover_map: Res<HoverMap>, hover_map: Res<HoverMap>,
time: Res<Time> time: Res<Time>,
) { ) {
let window = window.single(); let window = window.single();
let mut cam = cam.single_mut(); let mut cam = cam.single_mut();
let map_ui_entity = map_ui_entity.single(); let map_ui_entity = map_ui_entity.single();
let ps = hover_map.iter().filter_map(|(id, hit_map)| match id { let ps = hover_map
PointerId::Mouse => window.cursor_position().map(|p| .iter()
match pointers.0.get(id) { .filter_map(|(id, hit_map)| {
Some(p_cache) => (p_cache.1.filter(|_| mouse_buttons.pressed(MouseButton::Left)), p, p_cache.0), match id {
None => (None, p, p) PointerId::Mouse => window.cursor_position().map(|p| match pointers.0.get(id) {
} Some(p_cache) => (
), p_cache
PointerId::Touch(i) => touches.get_pressed(*i).map(|t| (pointers.0.get(id).map(|(pos, start)| (*start).unwrap_or(time.elapsed())), t.position(), t.previous_position())), .1
_ => None .filter(|_| mouse_buttons.pressed(MouseButton::Left)),
}.map(|(pressed_start,new_pos, old_pos)| (pressed_start,new_pos,old_pos,id,hit_map)) p,
).collect::<Vec<_>>(); p_cache.0,
let pressed_on_map = ps.iter().filter(|p| p.0.is_some() && p.4.contains_key(&map_ui_entity)).collect::<Vec<_>>(); ),
None => (None, p, p),
}),
PointerId::Touch(i) => touches.get_pressed(*i).map(|t| {
(
pointers
.0
.get(id)
.map(|(pos, start)| (*start).unwrap_or(time.elapsed())),
t.position(),
t.previous_position(),
)
}),
_ => None,
}
.map(|(pressed_start, new_pos, old_pos)| (pressed_start, new_pos, old_pos, id, hit_map))
})
.collect::<Vec<_>>();
let pressed_on_map = ps
.iter()
.filter(|p| p.0.is_some() && p.4.contains_key(&map_ui_entity))
.collect::<Vec<_>>();
let old_midpoint = pressed_on_map.iter().fold(Vec2::ZERO, |acc, (_, _, old_pos, _, _)| { let old_midpoint = pressed_on_map
acc + (old_pos/pressed_on_map.len() as f32) .iter()
}); .fold(Vec2::ZERO, |acc, (_, _, old_pos, _, _)| {
let new_midpoint = pressed_on_map.iter().fold(Vec2::ZERO, |acc, (_, new_pos, _, _, _)| { acc + (old_pos / pressed_on_map.len() as f32)
acc + (new_pos/pressed_on_map.len() as f32) });
}); let new_midpoint = pressed_on_map
.iter()
.fold(Vec2::ZERO, |acc, (_, new_pos, _, _, _)| {
acc + (new_pos / pressed_on_map.len() as f32)
});
// move camera // move camera
cam.translation.x -= (new_midpoint.x - old_midpoint.x)*cam.scale.x; cam.translation.x -= (new_midpoint.x - old_midpoint.x) * cam.scale.x;
cam.translation.y += (new_midpoint.y - old_midpoint.y)*cam.scale.y; cam.translation.y += (new_midpoint.y - old_midpoint.y) * cam.scale.y;
// multiple fingers zoom // multiple fingers zoom
if pressed_on_map.len() > 1 { if pressed_on_map.len() > 1 {
let old_d_to_midpoint = pressed_on_map.iter().fold(0., |acc, (_, _, old_pos, _, _)| { let old_d_to_midpoint = pressed_on_map
acc + (old_midpoint-old_pos).norm() .iter()
}); .fold(0., |acc, (_, _, old_pos, _, _)| {
let new_d_to_midpoint = pressed_on_map.iter().fold(0., |acc, (_, new_pos, _, _, _)| { acc + (old_midpoint - old_pos).norm()
acc + (new_midpoint-new_pos).norm() });
}); let new_d_to_midpoint = pressed_on_map
let zoom = new_d_to_midpoint/old_d_to_midpoint; .iter()
.fold(0., |acc, (_, new_pos, _, _, _)| {
acc + (new_midpoint - new_pos).norm()
});
let zoom = new_d_to_midpoint / old_d_to_midpoint;
cam.scale /= zoom; cam.scale /= zoom;
} }
// mouse scroll zoom // mouse scroll zoom
for ev in ev_scroll.read() { for ev in ev_scroll.read() {
let scale = (cam.scale.x-(ev.y*0.1/window.width().min(window.height()))).clamp(0.0001, 2./window.width().min(window.height())); let scale = (cam.scale.x - (ev.y * 0.1 / window.width().min(window.height())))
.clamp(0.0001, 2. / window.width().min(window.height()));
cam.scale = Vec3::new(scale, scale, scale); cam.scale = Vec3::new(scale, scale, scale);
} }
@ -84,8 +118,12 @@ fn move_cam(
pointers.0.clear(); pointers.0.clear();
for (pressed_start, new_pos, _, id, _) in ps { for (pressed_start, new_pos, _, id, _) in ps {
match id { match id {
PointerId::Mouse => {pointers.0.insert(*id, (new_pos, pressed_start));}, PointerId::Mouse => {
PointerId::Touch(_) => {pointers.0.insert(*id, (new_pos, pressed_start));}, pointers.0.insert(*id, (new_pos, pressed_start));
}
PointerId::Touch(_) => {
pointers.0.insert(*id, (new_pos, pressed_start));
}
_ => {} _ => {}
} }
} }

View File

@ -1,12 +1,15 @@
#![feature(duration_constructors)] #![feature(duration_constructors)]
use bevy::{prelude::*, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}}; use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*,
};
use bevy_inspector_egui::quick::WorldInspectorPlugin; use bevy_inspector_egui::quick::WorldInspectorPlugin;
pub mod map;
pub mod camera;
pub mod ui;
pub mod time;
pub mod animals; pub mod animals;
pub mod camera;
pub mod map;
pub mod time;
pub mod ui;
#[bevy_main] #[bevy_main]
pub fn main() { pub fn main() {
@ -23,7 +26,7 @@ pub fn main() {
LogDiagnosticsPlugin { LogDiagnosticsPlugin {
filter: Some(vec![FrameTimeDiagnosticsPlugin::FPS]), filter: Some(vec![FrameTimeDiagnosticsPlugin::FPS]),
..Default::default() ..Default::default()
} },
)) ))
.run(); .run();
} }

View File

@ -1,15 +1,20 @@
use std::time::Duration; use std::time::Duration;
use bevy::{asset::RenderAssetUsages, picking::PickSet, prelude::*, render::mesh::{Indices, PrimitiveTopology}}; use bevy::{
asset::RenderAssetUsages,
picking::PickSet,
prelude::*,
render::mesh::{Indices, PrimitiveTopology},
};
use noise::{Fbm, MultiFractal, NoiseFn, Perlin}; use noise::{Fbm, MultiFractal, NoiseFn, Perlin};
use rand::{thread_rng, Rng, SeedableRng}; use rand::{thread_rng, Rng, SeedableRng};
use voronoice::{BoundingBox, Point, VoronoiBuilder}; use voronoice::{BoundingBox, Point, VoronoiBuilder};
mod cells; mod cells;
mod picking; mod picking;
use picking::*;
use cells::*;
pub use cells::CellKind; pub use cells::CellKind;
use cells::*;
use picking::*;
use crate::{time::GameTime, ui::CurrentAction}; use crate::{time::GameTime, ui::CurrentAction};
@ -35,16 +40,16 @@ pub const SIZE: usize = 10000;
struct Seed(u32); struct Seed(u32);
#[derive(Component)] #[derive(Component)]
pub struct Voronoi (voronoice::Voronoi); pub struct Voronoi(voronoice::Voronoi);
#[derive(Component)] #[derive(Component)]
pub struct MapMarker; pub struct MapMarker;
#[derive(Component)] #[derive(Component)]
struct MapColors (Vec<[f32; 4]>); struct MapColors(Vec<[f32; 4]>);
#[derive(Component)] #[derive(Component)]
pub struct CellsEntities (Vec<Entity>); pub struct CellsEntities(Vec<Entity>);
#[derive(Component)] #[derive(Component)]
pub struct MeshNeedsUpdate(bool); pub struct MeshNeedsUpdate(bool);
@ -53,12 +58,15 @@ fn setup(
mut cmds: Commands, mut cmds: Commands,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>, mut materials: ResMut<Assets<ColorMaterial>>,
seed: Res<Seed> seed: Res<Seed>,
) { ) {
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed.0 as u64); let mut rng = rand::rngs::SmallRng::seed_from_u64(seed.0 as u64);
let mut sites = Vec::with_capacity(SIZE); let mut sites = Vec::with_capacity(SIZE);
for _ in 0..SIZE { for _ in 0..SIZE {
sites.push(Point { x:rng.gen_range(-WIDTH/2.0..WIDTH/2.0) as f64, y:rng.gen_range(-HEIGHT/2.0..HEIGHT/2.0) as f64 }) sites.push(Point {
x: rng.gen_range(-WIDTH / 2.0..WIDTH / 2.0) as f64,
y: rng.gen_range(-HEIGHT / 2.0..HEIGHT / 2.0) as f64,
})
} }
let voronoi = VoronoiBuilder::default() let voronoi = VoronoiBuilder::default()
.set_sites(sites) .set_sites(sites)
@ -68,19 +76,22 @@ fn setup(
.unwrap(); .unwrap();
let mut cells = Vec::with_capacity(SIZE); let mut cells = Vec::with_capacity(SIZE);
let z_noise = Fbm::<Perlin>::new(seed.0); let z_noise = Fbm::<Perlin>::new(seed.0);
let moisture_noise = Fbm::<Perlin>::new(seed.0+1) let moisture_noise = Fbm::<Perlin>::new(seed.0 + 1).set_frequency(2.);
.set_frequency(2.);
for i in 0..SIZE { for i in 0..SIZE {
let c = voronoi.cell(i); let c = voronoi.cell(i);
let site = c.site_position(); let site = c.site_position();
let z = ( let z = (
0.3 // Arbitrary value 0.3 // Arbitrary value
+ ((z_noise.get([site.x, site.y])+1.)/2.) // Noise + [0; 1] + ((z_noise.get([site.x, site.y])+1.)/2.) // Noise + [0; 1]
- ((site.x.powi(2)+site.y.powi(2)).sqrt()*0.5) // Distance - [0; sqrt(2)] * 0.5 - ((site.x.powi(2)+site.y.powi(2)).sqrt()*0.5)
).clamp(0., 1.); // Distance - [0; sqrt(2)] * 0.5
)
.clamp(0., 1.);
let m = ( let m = (
(moisture_noise.get([site.x, site.y])+1.)/2. // Noise + [0; 1] (moisture_noise.get([site.x, site.y]) + 1.) / 2.
).clamp(0., 1.) as f32; // Noise + [0; 1]
)
.clamp(0., 1.) as f32;
let k = if z <= 0.5 { let k = if z <= 0.5 {
CellKind::Sea CellKind::Sea
} else if z <= 0.52 { } else if z <= 0.52 {
@ -93,40 +104,40 @@ fn setup(
cells.push(Cell { cells.push(Cell {
kind: k, kind: k,
voronoi_id: i, voronoi_id: i,
altitude: (z*255.) as u8, altitude: (z * 255.) as u8,
vertices: vec![] vertices: vec![],
}); });
} }
let mut poss = Vec::new(); let mut poss = Vec::new();
let mut indices = Vec::new(); let mut indices = Vec::new();
for (c, cd) in voronoi.iter_cells().zip(cells.iter_mut()).filter(|(_,cd)| cd.kind != CellKind::Forest) { for (c, cd) in voronoi
.iter_cells()
.zip(cells.iter_mut())
.filter(|(_, cd)| cd.kind != CellKind::Forest)
{
let vs = c.iter_vertices().collect::<Vec<_>>(); let vs = c.iter_vertices().collect::<Vec<_>>();
let i = poss.len(); let i = poss.len();
for v in vs.iter() { for v in vs.iter() {
poss.push(Vec3::new(v.x as f32, v.y as f32, 0.)); poss.push(Vec3::new(v.x as f32, v.y as f32, 0.));
} }
for v in 1..(vs.len()-1) { for v in 1..(vs.len() - 1) {
indices.extend_from_slice(&[i as u32, (i+v) as u32, (i+v+1) as u32]); indices.extend_from_slice(&[i as u32, (i + v) as u32, (i + v + 1) as u32]);
cd.vertices.extend_from_slice(&[i, i+v, i+v+1]); cd.vertices.extend_from_slice(&[i, i + v, i + v + 1]);
} }
} }
let colors = vec![[0.; 4]; poss.len()]; let colors = vec![[0.; 4]; poss.len()];
let mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default()) let mesh = Mesh::new(
// Add 4 vertices, each with its own position attribute (coordinate in PrimitiveTopology::TriangleList,
// 3D space), for each of the corners of the parallelogram. RenderAssetUsages::default(),
.with_inserted_attribute( )
Mesh::ATTRIBUTE_POSITION, // Add 4 vertices, each with its own position attribute (coordinate in
poss // 3D space), for each of the corners of the parallelogram.
) .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, poss)
.with_inserted_attribute( .with_inserted_attribute(Mesh::ATTRIBUTE_COLOR, colors.clone())
Mesh::ATTRIBUTE_COLOR, .with_inserted_indices(Indices::U32(indices));
colors.clone()
)
.with_inserted_indices(Indices::U32(indices));
let mut cells_entities = Vec::with_capacity(cells.len()); let mut cells_entities = Vec::with_capacity(cells.len());
cmds.spawn(( cmds.spawn((
@ -136,74 +147,35 @@ fn setup(
Voronoi(voronoi), Voronoi(voronoi),
MapColors(colors), MapColors(colors),
MeshNeedsUpdate(true), MeshNeedsUpdate(true),
MapMarker MapMarker,
)).with_children(|parent| { ))
.with_children(|parent| {
for cell in cells { for cell in cells {
let kind = cell.kind; let kind = cell.kind;
let mut cmd = parent.spawn(cell); let mut cmd = parent.spawn(cell);
match kind { match kind {
CellKind::Grass | CellKind::Forest => { CellKind::Grass | CellKind::Forest => {
cmd.insert((Wealth(0), Regeneration { cmd.insert((
last_update: Duration::ZERO, Wealth(0),
full_growth_duration: kind.regen_full_growth_duration() Regeneration {
})); last_update: Duration::ZERO,
}, full_growth_duration: kind.regen_full_growth_duration(),
},
));
}
_ => {} _ => {}
} }
cmd.observe(| cmd.observe(self::cells::on_click);
trigger: Trigger<Pointer<Click>>,
mut cells: Query<&mut Cell>,
mut map_needs_update: Query<&mut MeshNeedsUpdate, With<MapMarker>>,
mut cmds: Commands,
ca: Res<CurrentAction>,
gt: Res<GameTime>,
| {
if trigger.duration > Duration::from_millis(200) {
return
}
match *ca {
CurrentAction::ChangeCell(ck) => {
let mut cell = cells.get_mut(trigger.target).unwrap();
match ck {
CellKind::Forest => match cell.kind {
CellKind::Dirt | CellKind::Grass => {
cmds.entity(trigger.target).insert((Wealth(0), Regeneration {
last_update: gt.current,
full_growth_duration: CellKind::Forest.regen_full_growth_duration()
}));
cell.kind = CellKind::Forest;
map_needs_update.single_mut().0 = true;
},
_ => {}
},
CellKind::Grass => match cell.kind {
CellKind::Dirt => {
cmds.entity(trigger.target).insert((Wealth(0), Regeneration {
last_update: gt.current,
full_growth_duration: CellKind::Grass.regen_full_growth_duration()
}));
cell.kind = CellKind::Grass;
map_needs_update.single_mut().0 = true;
},
_ => {}
}
_ => {}
}
}
_ => {}
}
});
cells_entities.push(cmd.id()); cells_entities.push(cmd.id());
} }
}).insert(CellsEntities(cells_entities)); })
.insert(CellsEntities(cells_entities));
} }
fn update_map_mesh( fn update_map_mesh(
cells: Query<(&Cell, Option<&Wealth>)>, cells: Query<(&Cell, Option<&Wealth>)>,
mut map: Query<(&Mesh2d, &mut MapColors, &mut MeshNeedsUpdate), With<MapMarker>>, mut map: Query<(&Mesh2d, &mut MapColors, &mut MeshNeedsUpdate), With<MapMarker>>,
mut meshes: ResMut<Assets<Mesh>> mut meshes: ResMut<Assets<Mesh>>,
) { ) {
let (mesh, mut cols, mut needs_update) = map.single_mut(); let (mesh, mut cols, mut needs_update) = map.single_mut();
if needs_update.0 { if needs_update.0 {

View File

@ -14,14 +14,14 @@ pub enum CellKind {
Forest, Forest,
Dirt, Dirt,
Stone, Stone,
Grass Grass,
} }
impl CellKind { impl CellKind {
pub fn regen_full_growth_duration(&self) -> Duration { pub fn regen_full_growth_duration(&self) -> Duration {
match self { match self {
CellKind::Sea | CellKind::Beach | CellKind::Dirt | CellKind::Stone => unreachable!(), CellKind::Sea | CellKind::Beach | CellKind::Dirt | CellKind::Stone => unreachable!(),
CellKind::Forest => Duration::from_days(365*100), // Let's say that a forest takes 100 years to mature CellKind::Forest => Duration::from_days(365 * 100), // Let's say that a forest takes 100 years to mature
CellKind::Grass => Duration::from_weeks(7) // Let's say that grass takes 7 weeks to reach its max CellKind::Grass => Duration::from_weeks(7), // Let's say that grass takes 7 weeks to reach its max
} }
} }
} }
@ -31,17 +31,27 @@ pub struct Cell {
pub kind: CellKind, pub kind: CellKind,
pub voronoi_id: usize, pub voronoi_id: usize,
pub altitude: u8, pub altitude: u8,
pub vertices: Vec<usize> pub vertices: Vec<usize>,
} }
impl Cell { impl Cell {
pub fn color(&self, wealth: u8) -> [f32; 4] { pub fn color(&self, wealth: u8) -> [f32; 4] {
match self.kind { match self.kind {
CellKind::Sea => [0., 0., 1., 1.], CellKind::Sea => [0., 0., 1., 1.],
CellKind::Beach => [0.82, 0.84, 0.51, 1.], CellKind::Beach => [0.82, 0.84, 0.51, 1.],
CellKind::Forest => [0., 0.5 - (wealth as f32/255.*0.4), 0., 1.], CellKind::Forest => [0., 0.5 - (wealth as f32 / 255. * 0.4), 0., 1.],
CellKind::Dirt => [0.53 - (wealth as f32/255.*0.4), 0.38-(wealth as f32/255.*0.4), 0.29-(wealth as f32/255.*0.4), 1.], CellKind::Dirt => [
0.53 - (wealth as f32 / 255. * 0.4),
0.38 - (wealth as f32 / 255. * 0.4),
0.29 - (wealth as f32 / 255. * 0.4),
1.,
],
CellKind::Stone => [0.5, 0.5, 0.5, 1.], CellKind::Stone => [0.5, 0.5, 0.5, 1.],
CellKind::Grass => [(136./255.) - (wealth as f32/255.*0.15), (154./255.) + (wealth as f32/255.*0.1), (59./255.) - (wealth as f32/255.*0.15), 1.] CellKind::Grass => [
(136. / 255.) - (wealth as f32 / 255. * 0.15),
(154. / 255.) + (wealth as f32 / 255. * 0.1),
(59. / 255.) - (wealth as f32 / 255. * 0.15),
1.,
],
} }
} }
} }
@ -59,17 +69,17 @@ impl Default for Wealth {
#[require(Wealth)] #[require(Wealth)]
pub struct Regeneration { pub struct Regeneration {
pub last_update: Duration, pub last_update: Duration,
pub full_growth_duration: Duration pub full_growth_duration: Duration,
} }
pub fn cells_regeneration( pub fn cells_regeneration(
mut cells: Query<(&mut Regeneration, &mut Wealth)>, mut cells: Query<(&mut Regeneration, &mut Wealth)>,
mut map_needs_update: Query<&mut MeshNeedsUpdate, With<MapMarker>>, mut map_needs_update: Query<&mut MeshNeedsUpdate, With<MapMarker>>,
gt: Res<GameTime> gt: Res<GameTime>,
) { ) {
let mut map_needs_update = map_needs_update.single_mut(); let mut map_needs_update = map_needs_update.single_mut();
for (mut regen, mut wealth) in cells.iter_mut() { for (mut regen, mut wealth) in cells.iter_mut() {
if gt.current - regen.last_update > regen.full_growth_duration/u8::MAX as u32 { if gt.current - regen.last_update > regen.full_growth_duration / u8::MAX as u32 {
regen.last_update = gt.current; regen.last_update = gt.current;
wealth.0 = wealth.0.saturating_add(1); wealth.0 = wealth.0.saturating_add(1);
map_needs_update.0 = true; map_needs_update.0 = true;
@ -82,7 +92,7 @@ pub fn expand(
map: Query<(&Voronoi, &CellsEntities)>, map: Query<(&Voronoi, &CellsEntities)>,
mut cmds: Commands, mut cmds: Commands,
t: Res<Time>, t: Res<Time>,
gt: Res<GameTime> gt: Res<GameTime>,
) { ) {
let (voronoi, cells_entities) = map.single(); let (voronoi, cells_entities) = map.single();
let mut random = thread_rng(); let mut random = thread_rng();
@ -93,16 +103,37 @@ pub fn expand(
let wealth = if cell.kind == CellKind::Forest { let wealth = if cell.kind == CellKind::Forest {
1. 1.
} else { } else {
wealth.unwrap().0 as f64/255. wealth.unwrap().0 as f64 / 255.
}; };
if random.gen_bool((t.elapsed_secs_f64()*(gt.speed as f64)*wealth/(60.*60.*24.*30.)).min(1.)) { // Let say that grass takes 1 months to expand if random.gen_bool(
let target = voronoi.0.cell(cell.voronoi_id).iter_neighbors().choose(&mut random).unwrap(); (t.elapsed_secs_f64() * (gt.speed as f64) * wealth / (60. * 60. * 24. * 30.))
.min(1.),
) {
// Let say that grass takes 1 months to expand
let target = voronoi
.0
.cell(cell.voronoi_id)
.iter_neighbors()
.choose(&mut random)
.unwrap();
changes.push((target, CellKind::Grass)); changes.push((target, CellKind::Grass));
} }
} }
if cell.kind == CellKind::Forest { if cell.kind == CellKind::Forest {
if random.gen_bool((t.elapsed_secs_f64()*(gt.speed as f64)*(wealth.unwrap().0 as f64/255.).sqrt()/(60.*60.*24.*365.*5.)).min(1.)) { // Let say that forest takes 5 years to expand if random.gen_bool(
let target = voronoi.0.cell(cell.voronoi_id).iter_neighbors().choose(&mut random).unwrap(); (t.elapsed_secs_f64()
* (gt.speed as f64)
* (wealth.unwrap().0 as f64 / 255.).sqrt()
/ (60. * 60. * 24. * 365. * 5.))
.min(1.),
) {
// Let say that forest takes 5 years to expand
let target = voronoi
.0
.cell(cell.voronoi_id)
.iter_neighbors()
.choose(&mut random)
.unwrap();
changes.push((target, CellKind::Forest)); changes.push((target, CellKind::Forest));
} }
} }
@ -110,18 +141,75 @@ pub fn expand(
for (v_id, kind) in changes { for (v_id, kind) in changes {
let target = cells_entities.0[v_id]; let target = cells_entities.0[v_id];
let (mut cell, _) = cells.get_mut(target).unwrap(); let (mut cell, _) = cells.get_mut(target).unwrap();
if kind == CellKind::Forest && (cell.kind == CellKind::Dirt || cell.kind == CellKind::Grass) { if kind == CellKind::Forest && (cell.kind == CellKind::Dirt || cell.kind == CellKind::Grass)
{
cell.kind = CellKind::Forest; cell.kind = CellKind::Forest;
cmds.entity(target).insert((Wealth(0), Regeneration { cmds.entity(target).insert((
full_growth_duration: CellKind::Forest.regen_full_growth_duration(), Wealth(0),
last_update: gt.current Regeneration {
})); full_growth_duration: CellKind::Forest.regen_full_growth_duration(),
last_update: gt.current,
},
));
} else if kind == CellKind::Grass && cell.kind == CellKind::Dirt { } else if kind == CellKind::Grass && cell.kind == CellKind::Dirt {
cell.kind = CellKind::Grass; cell.kind = CellKind::Grass;
cmds.entity(target).insert((Wealth(0), Regeneration { cmds.entity(target).insert((
full_growth_duration: CellKind::Grass.regen_full_growth_duration(), Wealth(0),
last_update: gt.current Regeneration {
})); full_growth_duration: CellKind::Grass.regen_full_growth_duration(),
last_update: gt.current,
},
));
} }
} }
} }
pub fn on_click(
trigger: Trigger<Pointer<Click>>,
mut cells: Query<&mut Cell>,
mut map_needs_update: Query<&mut MeshNeedsUpdate, With<MapMarker>>,
mut cmds: Commands,
ca: Res<CurrentAction>,
gt: Res<GameTime>,
) {
if trigger.duration > Duration::from_millis(200) {
return;
}
match *ca {
CurrentAction::ChangeCell(ck) => {
let mut cell = cells.get_mut(trigger.target).unwrap();
match ck {
CellKind::Forest => match cell.kind {
CellKind::Dirt | CellKind::Grass => {
cmds.entity(trigger.target).insert((
Wealth(0),
Regeneration {
last_update: gt.current,
full_growth_duration: CellKind::Forest.regen_full_growth_duration(),
},
));
cell.kind = CellKind::Forest;
map_needs_update.single_mut().0 = true;
}
_ => {}
},
CellKind::Grass => match cell.kind {
CellKind::Dirt => {
cmds.entity(trigger.target).insert((
Wealth(0),
Regeneration {
last_update: gt.current,
full_growth_duration: CellKind::Grass.regen_full_growth_duration(),
},
));
cell.kind = CellKind::Grass;
map_needs_update.single_mut().0 = true;
}
_ => {}
},
_ => {}
}
}
_ => {}
}
}

View File

@ -1,4 +1,11 @@
use bevy::{picking::{backend::{HitData, PointerHits}, pointer::{PointerId, PointerLocation}}, prelude::*, window::PrimaryWindow}; use bevy::{
picking::{
backend::{HitData, PointerHits},
pointer::{PointerId, PointerLocation},
},
prelude::*,
window::PrimaryWindow,
};
use voronoice::Point; use voronoice::Point;
use super::{CellsEntities, MapMarker, Voronoi}; use super::{CellsEntities, MapMarker, Voronoi};
@ -8,7 +15,7 @@ pub fn picking_backend(
window: Query<&Window, With<PrimaryWindow>>, window: Query<&Window, With<PrimaryWindow>>,
pointers: Query<(&PointerId, &PointerLocation)>, pointers: Query<(&PointerId, &PointerLocation)>,
map: Query<(&Voronoi, &CellsEntities, &Transform), With<MapMarker>>, map: Query<(&Voronoi, &CellsEntities, &Transform), With<MapMarker>>,
mut output: EventWriter<PointerHits> mut output: EventWriter<PointerHits>,
) { ) {
let (cam, cam_id) = cam.single(); let (cam, cam_id) = cam.single();
let window = window.single(); let window = window.single();
@ -16,24 +23,38 @@ pub fn picking_backend(
let mut last_cell = 0; let mut last_cell = 0;
for (id, l) in pointers.iter() { for (id, l) in pointers.iter() {
if let Some(mut pos) = l.location().map(|l| l.position) { if let Some(mut pos) = l.location().map(|l| l.position) {
pos -= window.size()/2.; pos -= window.size() / 2.;
pos *= cam.scale.xy(); pos *= cam.scale.xy();
pos.x += cam.translation.x; pos.x += cam.translation.x;
pos.y -= cam.translation.y; pos.y -= cam.translation.y;
if let Some(c) = voronoi.0.cell(last_cell).iter_path(Point { x: pos.x as f64, y: -pos.y as f64 }).last() { if let Some(c) = voronoi
.0
.cell(last_cell)
.iter_path(Point {
x: pos.x as f64,
y: -pos.y as f64,
})
.last()
{
last_cell = c; last_cell = c;
output.send(PointerHits { output.send(PointerHits {
pointer: *id, pointer: *id,
picks: vec![(cells_entities.0[c], HitData { picks: vec![(
camera: cam_id, cells_entities.0[c],
depth: map_pos.translation.z, HitData {
position: Some(Vec3 { x: pos.x, y: pos.y, z: map_pos.translation.z }), camera: cam_id,
normal: None depth: map_pos.translation.z,
})], position: Some(Vec3 {
order: map_pos.translation.z x: pos.x,
y: pos.y,
z: map_pos.translation.z,
}),
normal: None,
},
)],
order: map_pos.translation.z,
}); });
} }
} }
} }
} }

View File

@ -6,23 +6,20 @@ pub struct Plugin;
impl bevy::prelude::Plugin for Plugin { impl bevy::prelude::Plugin for Plugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.insert_resource(GameTime { app.insert_resource(GameTime {
current: Duration::ZERO, current: Duration::ZERO,
speed: 24. * 60. * 60. * 10. speed: 24. * 60. * 60. * 10.,
}) })
.add_systems(PreUpdate, update_time); .add_systems(PreUpdate, update_time);
} }
} }
#[derive(Resource)] #[derive(Resource)]
pub struct GameTime { pub struct GameTime {
pub current: Duration, pub current: Duration,
pub speed: f32 // = game time / real time pub speed: f32, // = game time / real time
} }
fn update_time( fn update_time(mut gt: ResMut<GameTime>, time: Res<Time>) {
mut gt: ResMut<GameTime>,
time: Res<Time>
) {
let speed = gt.speed; let speed = gt.speed;
gt.current += Duration::from_secs_f32(time.delta_secs() * speed); gt.current += Duration::from_secs_f32(time.delta_secs() * speed);
} }

View File

@ -1,13 +1,24 @@
use bevy::{
asset::embedded_asset,
input::mouse::MouseWheel,
math::{NormedVectorSpace, VectorSpace},
picking::{focus::HoverMap, pointer::PointerId},
prelude::*,
utils::{dbg, HashMap},
window::PrimaryWindow,
};
use mevy::*; use mevy::*;
use bevy::{asset::embedded_asset, input::mouse::MouseWheel, math::{NormedVectorSpace, VectorSpace}, picking::{focus::HoverMap, pointer::PointerId}, prelude::*, utils::{dbg, HashMap}, window::PrimaryWindow};
use crate::{animals::AnimalKind, map::{self, CellKind, MapMarker}}; use crate::{
animals::AnimalKind,
map::{self, CellKind, MapMarker},
};
// #77767b // #77767b
const TABBAR_COLOR: Color = Color::srgb(119./255., 118./255., 123./255.); const TABBAR_COLOR: Color = Color::srgb(119. / 255., 118. / 255., 123. / 255.);
// #E8E8E8 // #E8E8E8
const ENABLED_BUTTON_COLOR: Color = Color::srgb(232./255., 232./255., 232./255.); const ENABLED_BUTTON_COLOR: Color = Color::srgb(232. / 255., 232. / 255., 232. / 255.);
pub struct Plugin; pub struct Plugin;
impl bevy::prelude::Plugin for Plugin { impl bevy::prelude::Plugin for Plugin {
@ -31,7 +42,7 @@ pub enum CurrentAction {
#[default] #[default]
None, None,
ChangeCell(CellKind), ChangeCell(CellKind),
AddAnimal(AnimalKind) AddAnimal(AnimalKind),
} }
#[derive(Component, Debug)] #[derive(Component, Debug)]
@ -40,12 +51,9 @@ struct PointersDragging(HashMap<PointerId, Vec2>);
#[derive(Component)] #[derive(Component)]
pub struct MapUIComponent; pub struct MapUIComponent;
fn setup( fn setup(mut world: Commands, asset_server: Res<AssetServer>) {
mut world: Commands,
asset_server: Res<AssetServer>
) {
// Spawn all ui elements as children of this one // Spawn all ui elements as children of this one
spawn!{ spawn! {
Node {width: 100%, height: 100%, display: Display::Flex, flex_direction: FlexDirection::Column, !}; Node {width: 100%, height: 100%, display: Display::Flex, flex_direction: FlexDirection::Column, !};
PickingBehavior { PickingBehavior {
should_block_lower: false, should_block_lower: false,
@ -182,14 +190,19 @@ fn zoom_with_scroll(
mut ev_scroll: EventReader<MouseWheel>, mut ev_scroll: EventReader<MouseWheel>,
hover_map: Res<HoverMap>, hover_map: Res<HoverMap>,
window: Query<&Window, With<PrimaryWindow>>, window: Query<&Window, With<PrimaryWindow>>,
map_ui_id: Query<Entity, With<MapUIComponent>> map_ui_id: Query<Entity, With<MapUIComponent>>,
) { ) {
let map_ui_id = map_ui_id.single(); let map_ui_id = map_ui_id.single();
if hover_map.get(&PointerId::Mouse).and_then(|hovered_ids| hovered_ids.get(&map_ui_id)).is_some() { if hover_map
.get(&PointerId::Mouse)
.and_then(|hovered_ids| hovered_ids.get(&map_ui_id))
.is_some()
{
let window = window.single(); let window = window.single();
let mut cam = cam.single_mut(); let mut cam = cam.single_mut();
for ev in ev_scroll.read() { for ev in ev_scroll.read() {
let scale = (cam.scale.x-(ev.y*0.1/window.width().min(window.height()))).clamp(0.0001, 2./window.width().min(window.height())); let scale = (cam.scale.x - (ev.y * 0.1 / window.width().min(window.height())))
.clamp(0.0001, 2. / window.width().min(window.height()));
cam.scale = Vec3::new(scale, scale, scale); cam.scale = Vec3::new(scale, scale, scale);
} }
} }