diff --git a/CHANGELOG.md b/CHANGELOG.md index a76f5cd71f..7e2970b08f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Map indicators for group members - Hot-reloading for i18n, sounds, loot lotteries, and more - Initial support for alternate style keyboards +- Flying birds travel the world ### Changed diff --git a/common/src/path.rs b/common/src/path.rs index 07eb1da4c2..367f455688 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -71,6 +71,8 @@ pub struct TraversalConfig { pub min_tgt_dist: f32, /// Whether the agent can climb. pub can_climb: bool, + /// Whether the agent can fly. + pub can_fly: bool, } const DIAGONALS: [Vec2; 8] = [ @@ -427,7 +429,7 @@ impl Chaser { .unwrap_or(false) }); - if !walking_towards_edge { + if !walking_towards_edge || traversal_cfg.can_fly { Some(((tgt - pos) * Vec3::new(1.0, 1.0, 0.0), 1.0)) } else { None @@ -545,14 +547,14 @@ where .filter(|_| { vol.get(pos).map(|b| !b.is_liquid()).unwrap_or(true) || traversal_cfg.can_climb + || traversal_cfg.can_fly }) .into_iter() .flatten(), ) .map(move |dir| (pos, dir)) .filter(move |(pos, dir)| { - is_walkable(pos) - && is_walkable(&(*pos + **dir)) + (traversal_cfg.can_fly || is_walkable(pos) && is_walkable(&(*pos + **dir))) && ((dir.z < 1 || vol .get(pos + Vec3::unit_z() * 2) diff --git a/common/sys/src/agent.rs b/common/sys/src/agent.rs index ae02157b6c..76bc5319fd 100644 --- a/common/sys/src/agent.rs +++ b/common/sys/src/agent.rs @@ -212,6 +212,7 @@ impl<'a> System<'a> for Sys { in_liquid: physics_state.in_liquid.is_some(), min_tgt_dist: 1.0, can_climb: body.map(|b| b.can_climb()).unwrap_or(false), + can_fly: body.map(|b| b.can_fly()).unwrap_or(false), }; let mut do_idle = false; @@ -221,6 +222,8 @@ impl<'a> System<'a> for Sys { match &mut agent.activity { Activity::Idle { bearing, chaser } => { if let Some(travel_to) = agent.rtsim_controller.travel_to { + //if it has an rtsim destination and can fly then it should + inputs.fly.set_state(traversal_config.can_fly); if let Some((bearing, speed)) = chaser.chase(&*terrain, pos.0, vel.0, travel_to, TraversalConfig { min_tgt_dist: 1.25, @@ -230,10 +233,31 @@ impl<'a> System<'a> for Sys { inputs.move_dir = bearing.xy().try_normalized().unwrap_or(Vec2::zero()) * speed.min(agent.rtsim_controller.speed_factor); - inputs.jump.set_state(bearing.z > 1.5); + inputs.jump.set_state(bearing.z > 1.5 || traversal_config.can_fly && traversal_config.on_ground); inputs.climb = Some(comp::Climb::Up); //.filter(|_| bearing.z > 0.1 || physics_state.in_liquid.is_some()); - inputs.move_z = bearing.z + 0.05; + + inputs.move_z = bearing.z + if traversal_config.can_fly { + if terrain + .ray( + pos.0 + Vec3::unit_z(), + pos.0 + + bearing + .try_normalized() + .unwrap_or(Vec3::unit_y()) + * 60.0 + + Vec3::unit_z(), + ) + .until(Block::is_solid) + .cast() + .1 + .map_or(true, |b| b.is_some()) + { + 1.0 //fly up when approaching obstacles + } else { -0.1 } //flying things should slowly come down from the stratosphere + } else { + 0.05 //normal land traveller offset + }; } } else { *bearing += Vec2::new( diff --git a/server/src/rtsim/entity.rs b/server/src/rtsim/entity.rs index 2680edd9c8..eb517d9b48 100644 --- a/server/src/rtsim/entity.rs +++ b/server/src/rtsim/entity.rs @@ -20,15 +20,42 @@ const PERM_SPECIES: u32 = 0; const PERM_BODY: u32 = 1; const PERM_LOADOUT: u32 = 2; const PERM_LEVEL: u32 = 3; +const PERM_GENUS: u32 = 4; impl Entity { pub fn rng(&self, perm: u32) -> impl Rng { RandomPerm::new(self.seed + perm) } pub fn get_body(&self) -> comp::Body { - let species = *(&comp::humanoid::ALL_SPECIES) - .choose(&mut self.rng(PERM_SPECIES)) - .unwrap(); - comp::humanoid::Body::random_with(&mut self.rng(PERM_BODY), &species).into() + match self.rng(PERM_GENUS).gen::() { + //we want 75% birds, 25% humans for now + x if x < 0.75 => { + let species = *(&comp::bird_medium::ALL_SPECIES) + .choose(&mut self.rng(PERM_SPECIES)) + .unwrap(); + comp::bird_medium::Body::random_with(&mut self.rng(PERM_BODY), &species).into() + }, + _ => { + let species = *(&comp::humanoid::ALL_SPECIES) + .choose(&mut self.rng(PERM_SPECIES)) + .unwrap(); + comp::humanoid::Body::random_with(&mut self.rng(PERM_BODY), &species).into() + }, + } + } + + pub fn get_name(&self) -> String { + use common::{generation::get_npc_name, npc::NPC_NAMES}; + let npc_names = NPC_NAMES.read(); + match self.get_body() { + comp::Body::BirdMedium(b) => { + get_npc_name(&npc_names.bird_medium, b.species).to_string() + }, + comp::Body::BirdSmall(_) => "Warbler".to_string(), + comp::Body::Dragon(b) => get_npc_name(&npc_names.dragon, b.species).to_string(), + comp::Body::Humanoid(b) => get_npc_name(&npc_names.humanoid, b.species).to_string(), + //TODO: finish match as necessary + _ => unimplemented!(), + } } pub fn get_level(&self) -> u32 { @@ -102,6 +129,10 @@ impl Entity { .civs() .sites .iter() + .filter(|s| match self.get_body() { + comp::Body::Humanoid(_) => s.1.is_settlement() | s.1.is_castle(), + _ => s.1.is_dungeon(), + }) .filter(|_| thread_rng().gen_range(0i32, 4) == 0) .min_by_key(|(_, site)| { let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32); diff --git a/server/src/rtsim/mod.rs b/server/src/rtsim/mod.rs index 6fe07d280b..0bf58d0c49 100644 --- a/server/src/rtsim/mod.rs +++ b/server/src/rtsim/mod.rs @@ -88,7 +88,7 @@ pub fn init(state: &mut State, #[cfg(feature = "worldgen")] world: &world::World #[cfg(not(feature = "worldgen"))] let mut rtsim = RtSim::new(Vec2::new(40, 40)); - for _ in 0..2500 { + for _ in 0..10000 { let pos = rtsim .chunks .size() diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs index dfe7ecd716..583c036874 100644 --- a/server/src/rtsim/tick.rs +++ b/server/src/rtsim/tick.rs @@ -98,13 +98,23 @@ impl<'a> System<'a> for Sys { let body = entity.get_body(); server_emitter.emit(ServerEvent::CreateNpc { pos: comp::Pos(spawn_pos), - stats: comp::Stats::new("Traveller".to_string(), body) - .with_level(entity.get_level()), + stats: comp::Stats::new(entity.get_name(), body).with_level(entity.get_level()), health: comp::Health::new(body, 10), - loadout: entity.get_loadout(&ability_map), + loadout: match body { + comp::Body::Humanoid(_) => entity.get_loadout(&ability_map), + _ => comp::Loadout::default(), + }, body, - agent: Some(comp::Agent::new(None, true, &body, false)), - alignment: comp::Alignment::Npc, + agent: Some(comp::Agent::new( + None, + matches!(body, comp::Body::Humanoid(_)), + &body, + false, + )), + alignment: match body { + comp::Body::Humanoid(_) => comp::Alignment::Npc, + _ => comp::Alignment::Wild, + }, scale: comp::Scale(1.0), drop_item: None, home_chunk: None, diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index a3f420eb88..11dd20f752 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -1071,6 +1071,12 @@ impl Site { }; self.population += years * self.population * (birth_rate - DEATH_RATE); } + + pub fn is_dungeon(&self) -> bool { matches!(self.kind, SiteKind::Dungeon) } + + pub fn is_settlement(&self) -> bool { matches!(self.kind, SiteKind::Settlement) } + + pub fn is_castle(&self) -> bool { matches!(self.kind, SiteKind::Castle) } } #[repr(u8)]