diff --git a/assets/voxygen/voxel/object/campfire_lit.vox b/assets/voxygen/voxel/object/campfire_lit.vox new file mode 100644 index 0000000000..12a87b01a5 --- /dev/null +++ b/assets/voxygen/voxel/object/campfire_lit.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97cf1fa4d3e9545264d435ffe5bfa0f19259bd8114612e175beec900599b34db +size 58944 diff --git a/common/src/astar.rs b/common/src/astar.rs index 51dbbcd73f..8bf0104469 100644 --- a/common/src/astar.rs +++ b/common/src/astar.rs @@ -119,8 +119,8 @@ where } pub enum PathResult { - None, - Exhausted, + None(Path), + Exhausted(Path), Path(Path), Pending, } @@ -134,6 +134,7 @@ pub struct Astar { cheapest_scores: HashMap, final_scores: HashMap, visited: HashSet, + lowest_cost: Option, } impl Astar { @@ -150,6 +151,7 @@ impl Astar { cheapest_scores: std::iter::once((start.clone(), 0.0)).collect(), final_scores: std::iter::once((start.clone(), heuristic(&start))).collect(), visited: std::iter::once(start).collect(), + lowest_cost: None, } } @@ -169,6 +171,7 @@ impl Astar { if satisfied(&node) { return PathResult::Path(self.reconstruct_path_to(node)); } else { + self.lowest_cost = Some(node.clone()); for neighbor in neighbors(&node) { let node_cheapest = self.cheapest_scores.get(&node).unwrap_or(&f32::MAX); let neighbor_cheapest = @@ -191,14 +194,24 @@ impl Astar { } } } else { - return PathResult::None; + return PathResult::None( + self.lowest_cost + .clone() + .map(|lc| self.reconstruct_path_to(lc)) + .unwrap_or_default(), + ); } self.iter += 1 } if self.iter >= self.max_iters { - PathResult::Exhausted + PathResult::Exhausted( + self.lowest_cost + .clone() + .map(|lc| self.reconstruct_path_to(lc)) + .unwrap_or_default(), + ) } else { PathResult::Pending } diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 7f29a3d190..940de56b0f 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -26,9 +26,10 @@ impl Component for Alignment { #[derive(Clone, Debug, Default)] pub struct Agent { pub chaser: Chaser, - pub target: Option, + pub target: Option<(EcsEntity, f64)>, pub owner: Option, pub patrol_origin: Option>, + pub wander_pos: Option>, } impl Agent { @@ -36,6 +37,11 @@ impl Agent { self.owner = Some(owner); self } + + pub fn with_patrol_origin(mut self, origin: Vec3) -> Self { + self.patrol_origin = Some(origin); + self + } } impl Component for Agent { diff --git a/common/src/comp/body/object.rs b/common/src/comp/body/object.rs index 07be6c4f3d..514a191bf1 100644 --- a/common/src/comp/body/object.rs +++ b/common/src/comp/body/object.rs @@ -54,6 +54,7 @@ pub enum Body { CraftingBench = 48, BoltFire = 49, ArrowSnake = 50, + CampfireLit = 51, } impl Body { @@ -63,7 +64,7 @@ impl Body { } } -const ALL_OBJECTS: [Body; 50] = [ +const ALL_OBJECTS: [Body; 51] = [ Body::Arrow, Body::Bomb, Body::Scarecrow, @@ -82,6 +83,7 @@ const ALL_OBJECTS: [Body; 50] = [ Body::Pumpkin4, Body::Pumpkin5, Body::Campfire, + Body::CampfireLit, Body::LanternGround, Body::LanternGroundOpen, Body::LanternStanding, diff --git a/common/src/comp/location.rs b/common/src/comp/location.rs index eeebf7ce4d..f2631e630d 100644 --- a/common/src/comp/location.rs +++ b/common/src/comp/location.rs @@ -20,3 +20,16 @@ impl Waypoint { impl Component for Waypoint { type Storage = FlaggedStorage>; } + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct WaypointArea(f32); + +impl Component for WaypointArea { + type Storage = FlaggedStorage>; +} + +impl Default for WaypointArea { + fn default() -> Self { + Self(5.0) + } +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index ec36ce2774..dc67dbf03f 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -30,7 +30,7 @@ pub use energy::{Energy, EnergySource}; pub use inputs::CanBuild; pub use inventory::{item, Inventory, InventoryUpdate, Item, ItemKind}; pub use last::Last; -pub use location::Waypoint; +pub use location::{Waypoint, WaypointArea}; pub use phys::{ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel}; pub use player::Player; pub use projectile::Projectile; diff --git a/common/src/event.rs b/common/src/event.rs index 9f2d18890b..ccf1786736 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -105,6 +105,7 @@ pub enum ServerEvent { alignment: comp::Alignment, scale: comp::Scale, }, + CreateWaypoint(Vec3), ClientDisconnect(EcsEntity), ChunkRequest(EcsEntity, Vec2), ChatCmd(EcsEntity, String), diff --git a/common/src/generation.rs b/common/src/generation.rs index d411f9c76c..c88ddc9c5c 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -1,11 +1,24 @@ use vek::*; -pub struct NpcInfo { +pub enum EntityKind { + Enemy, + Boss, + Waypoint, +} + +pub struct EntityInfo { pub pos: Vec3, - pub boss: bool, + pub kind: EntityKind, } #[derive(Default)] pub struct ChunkSupplement { - pub npcs: Vec, + pub entities: Vec, +} + +impl ChunkSupplement { + pub fn with_entity(mut self, entity: EntityInfo) -> Self { + self.entities.push(entity); + self + } } diff --git a/common/src/path.rs b/common/src/path.rs index 1839d951f6..c02fd840a5 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -9,11 +9,19 @@ use vek::*; // Path -#[derive(Default, Clone, Debug)] +#[derive(Clone, Debug)] pub struct Path { nodes: Vec, } +impl Default for Path { + fn default() -> Self { + Self { + nodes: Vec::default(), + } + } +} + impl FromIterator for Path { fn from_iter>(iter: I) -> Self { Self { @@ -134,7 +142,7 @@ impl Chaser { self.route = find_path(&mut self.astar, vol, pos, tgt).into(); } - Some(tgt - pos) + Some((tgt - pos) * Vec3::new(1.0, 1.0, 0.0)) } } } @@ -225,10 +233,14 @@ where *astar = None; path } - PathResult::Pending => Path::default(), - _ => { + PathResult::None(path) => { *astar = None; - Path::default() + path } + PathResult::Exhausted(path) => { + *astar = None; + path + } + PathResult::Pending => Path::default(), } } diff --git a/common/src/state.rs b/common/src/state.rs index 1059fb20b1..ba190df7ed 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -142,6 +142,7 @@ impl State { ecs.register::>(); ecs.register::(); ecs.register::(); + ecs.register::(); ecs.register::(); ecs.register::(); ecs.register::(); diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 2be0efafae..f7b97ecf1c 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -4,6 +4,7 @@ use crate::{ self, Agent, Alignment, CharacterState, Controller, MountState, MovementState::Glide, Pos, Stats, }, + state::Time, sync::{Uid, UidAllocator}, }; use rand::{seq::SliceRandom, thread_rng, Rng}; @@ -18,6 +19,7 @@ pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( Read<'a, UidAllocator>, + Read<'a, Time>, Entities<'a>, ReadStorage<'a, Pos>, ReadStorage<'a, Stats>, @@ -33,6 +35,7 @@ impl<'a> System<'a> for Sys { &mut self, ( uid_allocator, + time, entities, positions, stats, @@ -73,21 +76,25 @@ impl<'a> System<'a> for Sys { let mut inputs = &mut controller.inputs; const PET_DIST: f32 = 12.0; - const PATROL_DIST: f32 = 48.0; - const SIGHT_DIST: f32 = 18.0; + const PATROL_DIST: f32 = 32.0; + const SIGHT_DIST: f32 = 24.0; const MIN_ATTACK_DIST: f32 = 3.25; + const CHASE_TIME_MIN: f64 = 4.0; let mut chase_tgt = None; let mut choose_target = false; + let mut new_target = None; - if let Some(target) = agent.target { + if let Some((target, aggro_time)) = agent.target { // Chase / attack target if let (Some(tgt_pos), stats) = (positions.get(target), stats.get(target)) { if stats.map(|s| s.is_dead).unwrap_or(false) { // Don't target dead entities choose_target = true; - } else if pos.0.distance(tgt_pos.0) < SIGHT_DIST { - chase_tgt = Some((tgt_pos.0, 1.0, true)) + } else if pos.0.distance(tgt_pos.0) < SIGHT_DIST + || (time.0 - aggro_time) < CHASE_TIME_MIN + { + chase_tgt = Some((tgt_pos.0, 1.5, true)) } else { // Lose sight of enemies choose_target = true; @@ -95,19 +102,22 @@ impl<'a> System<'a> for Sys { } else { choose_target = true; } - } else if let Some(owner) = agent.owner { + } + + // Return to owner + if let Some(owner) = agent.owner { if let Some(tgt_pos) = positions.get(owner) { - if pos.0.distance(tgt_pos.0) > PET_DIST || agent.target.is_none() { + if pos.0.distance(tgt_pos.0) > PET_DIST { // Follow owner chase_tgt = Some((tgt_pos.0, 6.0, false)); - } else { + } else if agent.target.is_none() { choose_target = thread_rng().gen::() < 0.02; } } else { agent.owner = None; } } else if let Some(patrol_origin) = agent.patrol_origin { - if pos.0.distance(patrol_origin) < PATROL_DIST { + if pos.0.distance(patrol_origin) > PATROL_DIST { // Return to patrol origin chase_tgt = Some((patrol_origin, 64.0, false)); } @@ -120,15 +130,16 @@ impl<'a> System<'a> for Sys { match stats.health.last_change.1.cause { comp::HealthSource::Attack { by } => { if agent.target.is_none() { - agent.target = uid_allocator.retrieve_entity_internal(by.id()); + new_target = uid_allocator.retrieve_entity_internal(by.id()); } else if thread_rng().gen::() < 0.005 { - agent.target = uid_allocator.retrieve_entity_internal(by.id()); + new_target = uid_allocator.retrieve_entity_internal(by.id()); } } _ => {} } } + // Choose a new target if choose_target { // Search for new targets let entities = (&entities, &positions, &stats, alignments.maybe()) @@ -144,7 +155,12 @@ impl<'a> System<'a> for Sys { .map(|(e, _, _, _)| e) .collect::>(); - agent.target = (&entities).choose(&mut thread_rng()).cloned(); + new_target = (&entities).choose(&mut thread_rng()).cloned(); + } + + // Update target when attack begins + if let Some(tgt) = new_target { + agent.target = Some((tgt, time.0)); } // Chase target @@ -154,7 +170,7 @@ impl<'a> System<'a> for Sys { inputs.jump.set_state(bearing.z > 1.0); } - if pos.0.distance(tgt_pos) < MIN_ATTACK_DIST && aggressive { + if aggressive && pos.0.distance(tgt_pos) < MIN_ATTACK_DIST { inputs.look_dir = tgt_pos - pos.0; inputs.move_dir = Vec2::from(tgt_pos - pos.0) .try_normalized() @@ -162,110 +178,42 @@ impl<'a> System<'a> for Sys { * 0.01; inputs.primary.set_state(true); } - } - /* - match agent { - Agent::Wanderer(bearing) => { - *bearing += Vec2::new(rand::random::() - 0.5, rand::random::() - 0.5) - * 0.1 - - *bearing * 0.01 - - pos.0 * 0.0002; - - if bearing.magnitude_squared() > 0.001 { - inputs.move_dir = bearing.normalized(); - } - } - Agent::Pet { target, chaser } => { - // Run towards target. - if let Some(tgt_pos) = positions.get(*target) { - if let Some(bearing) = chaser.chase(&*terrain, pos.0, tgt_pos.0, 5.0) { + // We're not wandering + agent.wander_pos = None; + } else { + if let Some(wander_pos) = agent.wander_pos { + if pos.0.distance(wander_pos) < 4.0 { + agent.wander_pos = None; + } else { + if let Some(bearing) = agent.chaser.chase(&*terrain, pos.0, wander_pos, 3.0) + { inputs.move_dir = - Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero()); + Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero()) * 0.5; inputs.jump.set_state(bearing.z > 1.0); } - } else { - inputs.move_dir = Vec2::zero(); } } - Agent::Enemy { bearing, target } => { - const SIGHT_DIST: f32 = 18.0; - const MIN_ATTACK_DIST: f32 = 3.25; - let mut choose_new = false; - if let Some((Some(target_pos), Some(target_stats), Some(target_character))) = - target.map(|target| { - ( - positions.get(target), - stats.get(target), - character_states.get(target), - ) - }) - { - inputs.look_dir = target_pos.0 - pos.0; - - let dist = Vec2::::from(target_pos.0 - pos.0).magnitude(); - if target_stats.is_dead { - choose_new = true; - } else if dist < 0.001 { - // Probably can only happen when entities are at a different z-level - // since at the same level repulsion would keep them apart. - // Distinct from the first if block since we may want to change the - // behavior for this case. - choose_new = true; - } else if dist < MIN_ATTACK_DIST { - // Fight (and slowly move closer) - inputs.move_dir = - Vec2::::from(target_pos.0 - pos.0).normalized() * 0.01; - inputs.primary.set_state(true); - } else if dist < SIGHT_DIST { - inputs.move_dir = - Vec2::::from(target_pos.0 - pos.0).normalized() * 0.96; - - if rand::random::() < 0.02 { - inputs.roll.set_state(true); - } - - if target_character.movement == Glide && target_pos.0.z > pos.0.z + 5.0 - { - inputs.glide.set_state(true); - inputs.jump.set_state(true); - } + // Choose new wander position + if agent.wander_pos.is_none() || thread_rng().gen::() < 0.005 { + agent.wander_pos = if thread_rng().gen::() < 0.5 { + let max_dist = if agent.owner.is_some() { + PET_DIST } else { - choose_new = true; - } - } else { - *bearing += - Vec2::new(rand::random::() - 0.5, rand::random::() - 0.5) - * 0.1 - - *bearing * 0.005; - - inputs.move_dir = if bearing.magnitude_squared() > 0.001 { - bearing.normalized() - } else { - Vec2::zero() + PATROL_DIST }; - - choose_new = true; - } - - if choose_new && rand::random::() < 0.1 { - let entities = (&entities, &positions, &stats) - .join() - .filter(|(e, e_pos, e_stats)| { - (e_pos.0 - pos.0).magnitude() < SIGHT_DIST - && *e != entity - && !e_stats.is_dead - }) - .map(|(e, _, _)| e) - .collect::>(); - - let mut rng = thread_rng(); - *target = (&entities).choose(&mut rng).cloned(); - } + Some( + agent + .patrol_origin + .unwrap_or(pos.0) + .map(|e| e + (thread_rng().gen::() - 0.5) * max_dist), + ) + } else { + None + }; } } - */ debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and()); debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and()); diff --git a/server-cli/Cargo.toml b/server-cli/Cargo.toml index d1a981232e..b9d382ddcf 100644 --- a/server-cli/Cargo.toml +++ b/server-cli/Cargo.toml @@ -9,7 +9,7 @@ worldgen = ["server/worldgen"] default = ["worldgen"] [dependencies] -server = { package = "veloren-server", path = "../server" } +server = { package = "veloren-server", path = "../server", default-features = false } common = { package = "veloren-common", path = "../common" } log = "0.4.8" diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 391348c0ff..495b674ae4 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -657,6 +657,7 @@ fn handle_object(server: &mut Server, entity: EcsEntity, args: String, _action: Ok("pumpkin_4") => comp::object::Body::Pumpkin4, Ok("pumpkin_5") => comp::object::Body::Pumpkin5, Ok("campfire") => comp::object::Body::Campfire, + Ok("campfire_lit") => comp::object::Body::CampfireLit, Ok("lantern_ground") => comp::object::Body::LanternGround, Ok("lantern_ground_open") => comp::object::Body::LanternGroundOpen, Ok("lantern_2") => comp::object::Body::LanternStanding2, diff --git a/server/src/lib.rs b/server/src/lib.rs index e5d1ad20ad..064cf39496 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -847,6 +847,17 @@ impl Server { .build(); } + ServerEvent::CreateWaypoint(pos) => { + self.create_object(comp::Pos(pos), comp::object::Body::CampfireLit) + .with(comp::LightEmitter { + offset: Vec3::unit_z() * 0.5, + col: Rgb::new(1.0, 0.65, 0.2), + strength: 2.0, + }) + .with(comp::WaypointArea::default()) + .build(); + } + ServerEvent::ClientDisconnect(entity) => { // Tell other clients to remove from player list if let (Some(uid), Some(_)) = ( diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index d2ba658c2a..06fe790a2a 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -4,6 +4,7 @@ use common::{ assets, comp::{self, item, Player, Pos}, event::{EventBus, ServerEvent}, + generation::EntityKind, msg::ServerMsg, state::TerrainChanges, terrain::TerrainGrid, @@ -95,88 +96,92 @@ impl<'a> System<'a> for Sys { } // Handle chunk supplement - for npc in supplement.npcs { - const SPAWN_NPCS: &'static [fn() -> (String, comp::Body, Option)] = &[ - (|| { - ( - "Traveler".into(), - comp::Body::Humanoid(comp::humanoid::Body::random()), - Some(assets::load_expect_cloned("common.items.weapons.staff_1")), - ) - }) as _, - (|| { - ( - "Wolf".into(), - comp::Body::QuadrupedMedium(comp::quadruped_medium::Body::random()), - None, - ) - }) as _, - (|| { - ( - "Duck".into(), - comp::Body::BirdMedium(comp::bird_medium::Body::random()), - None, - ) - }) as _, - (|| { - ( - "Rat".into(), - comp::Body::Critter(comp::critter::Body::random()), - None, - ) - }) as _, - (|| { - ( - "Pig".into(), - comp::Body::QuadrupedSmall(comp::quadruped_small::Body::random()), - None, - ) - }), - ]; - let (name, mut body, main) = SPAWN_NPCS - .choose(&mut rand::thread_rng()) - .expect("SPAWN_NPCS is nonempty")( - ); - let mut stats = comp::Stats::new(name, body, main); + for entity in supplement.entities { + if let EntityKind::Waypoint = entity.kind { + server_emitter.emit(ServerEvent::CreateWaypoint(entity.pos)); + } else { + const SPAWN_NPCS: &'static [fn() -> (String, comp::Body, Option)] = &[ + (|| { + ( + "Traveler".into(), + comp::Body::Humanoid(comp::humanoid::Body::random()), + Some(assets::load_expect_cloned("common.items.weapons.staff_1")), + ) + }) as _, + (|| { + ( + "Wolf".into(), + comp::Body::QuadrupedMedium(comp::quadruped_medium::Body::random()), + None, + ) + }) as _, + (|| { + ( + "Duck".into(), + comp::Body::BirdMedium(comp::bird_medium::Body::random()), + None, + ) + }) as _, + (|| { + ( + "Rat".into(), + comp::Body::Critter(comp::critter::Body::random()), + None, + ) + }) as _, + (|| { + ( + "Pig".into(), + comp::Body::QuadrupedSmall(comp::quadruped_small::Body::random()), + None, + ) + }), + ]; + let (name, mut body, main) = SPAWN_NPCS + .choose(&mut rand::thread_rng()) + .expect("SPAWN_NPCS is nonempty")( + ); + let mut stats = comp::Stats::new(name, body, main); - let mut scale = 1.0; + let mut scale = 1.0; - // TODO: Remove this and implement scaling or level depending on stuff like species instead - stats.level.set_level(rand::thread_rng().gen_range(1, 4)); + // TODO: Remove this and implement scaling or level depending on stuff like species instead + stats.level.set_level(rand::thread_rng().gen_range(1, 4)); - if npc.boss { - if rand::random::() < 0.8 { - let hbody = comp::humanoid::Body::random(); - body = comp::Body::Humanoid(hbody); - stats = comp::Stats::new( - "Fearless Wanderer".to_string(), - body, - Some(assets::load_expect_cloned("common.items.weapons.hammer_1")), - ); + if let EntityKind::Boss = entity.kind { + if rand::random::() < 0.8 { + let hbody = comp::humanoid::Body::random(); + body = comp::Body::Humanoid(hbody); + stats = comp::Stats::new( + "Fearless Wanderer".to_string(), + body, + Some(assets::load_expect_cloned("common.items.weapons.hammer_1")), + ); + } + stats.level.set_level(rand::thread_rng().gen_range(8, 15)); + scale = 2.0 + rand::random::(); } - stats.level.set_level(rand::thread_rng().gen_range(8, 15)); - scale = 2.0 + rand::random::(); - } - stats.update_max_hp(); - stats - .health - .set_to(stats.health.maximum(), comp::HealthSource::Revive); - if let Some(item::Item { - kind: item::ItemKind::Tool { power, .. }, - .. - }) = &mut stats.equipment.main - { - *power = stats.level.level() * 3; + stats.update_max_hp(); + stats + .health + .set_to(stats.health.maximum(), comp::HealthSource::Revive); + if let Some(item::Item { + kind: item::ItemKind::Tool { power, .. }, + .. + }) = &mut stats.equipment.main + { + *power = stats.level.level() * 3; + } + server_emitter.emit(ServerEvent::CreateNpc { + pos: Pos(entity.pos), + stats, + body, + alignment: comp::Alignment::Enemy, + agent: comp::Agent::default().with_patrol_origin(entity.pos), + scale: comp::Scale(scale), + }) } - server_emitter.emit(ServerEvent::CreateNpc { - pos: Pos(npc.pos), - stats, - body, - alignment: comp::Alignment::Enemy, - agent: comp::Agent::default(), - scale: comp::Scale(scale), - }) } } diff --git a/server/src/test_world.rs b/server/src/test_world.rs index a4b1e8deaa..055fb43cb0 100644 --- a/server/src/test_world.rs +++ b/server/src/test_world.rs @@ -1,5 +1,5 @@ use common::{ - generation::{ChunkSupplement, NpcInfo}, + generation::{ChunkSupplement, EntityInfo, EntityKind}, terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, vol::{ReadVol, RectVolSize, Vox, WriteVol}, }; @@ -37,7 +37,10 @@ impl World { Block::empty(), TerrainChunkMeta::void(), ), - ChunkSupplement::default(), + ChunkSupplement::default().with_entity(EntityInfo { + pos: Vec3::::from(chunk_pos.map(|e| e as f32 * 32.0)) + Vec3::unit_z() * 256.0, + kind: EntityKind::Waypoint, + }), )) } } diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index 656d884b6c..641f8696f1 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -1713,6 +1713,7 @@ pub fn mesh_object(obj: object::Body) -> Mesh { Body::Pumpkin4 => ("object.pumpkin_4", Vec3::new(-5.0, -4.0, 0.0)), Body::Pumpkin5 => ("object.pumpkin_5", Vec3::new(-4.0, -5.0, 0.0)), Body::Campfire => ("object.campfire", Vec3::new(-9.0, -10.0, 0.0)), + Body::CampfireLit => ("object.campfire_lit", Vec3::new(-9.0, -10.0, 0.0)), Body::LanternGround => ("object.lantern_ground", Vec3::new(-3.5, -3.5, 0.0)), Body::LanternGroundOpen => ("object.lantern_ground_open", Vec3::new(-3.5, -3.5, 0.0)), Body::LanternStanding => ("object.lantern_standing", Vec3::new(-7.5, -3.5, 0.0)), diff --git a/world/src/lib.rs b/world/src/lib.rs index 787e038cf1..5dd87712a6 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -19,7 +19,7 @@ use crate::{ util::Sampler, }; use common::{ - generation::{ChunkSupplement, NpcInfo}, + generation::{ChunkSupplement, EntityInfo, EntityKind}, terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, vol::{ReadVol, RectVolSize, Vox, WriteVol}, }; @@ -150,13 +150,17 @@ impl World { const SPAWN_RATE: f32 = 0.1; const BOSS_RATE: f32 = 0.03; let supplement = ChunkSupplement { - npcs: if rand::thread_rng().gen::() < SPAWN_RATE + entities: if rand::thread_rng().gen::() < SPAWN_RATE && sim_chunk.chaos < 0.5 && !sim_chunk.is_underwater { - vec![NpcInfo { + vec![EntityInfo { pos: gen_entity_pos(), - boss: rand::thread_rng().gen::() < BOSS_RATE, + kind: if rand::thread_rng().gen::() < BOSS_RATE { + EntityKind::Boss + } else { + EntityKind::Enemy + }, }] } else { Vec::new()