Fixed sprite AO, overhauled entity spawning, better enemy spawning in dungeons, made agents more skilled at combat

This commit is contained in:
Joshua Barretto 2020-04-18 21:26:43 +01:00
parent d0641ecbe3
commit 6448c17110
14 changed files with 376 additions and 150 deletions

View File

@ -5,6 +5,7 @@
in vec3 f_pos;
flat in vec3 f_norm;
in vec3 f_col;
in float f_ao;
in float f_light;
out vec4 tgt_color;
@ -24,6 +25,9 @@ void main() {
vec3 point_light = light_at(f_pos, f_norm);
light += point_light;
diffuse_light += point_light;
float ao = pow(f_ao, 0.5) * 0.85 + 0.15;
ambient_light *= ao;
diffuse_light *= ao;
vec3 surf_color = illuminate(f_col, light, diffuse_light, ambient_light);
float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x);

View File

@ -6,6 +6,7 @@
in vec3 v_pos;
in vec3 v_norm;
in vec3 v_col;
in float v_ao;
in vec4 inst_mat0;
in vec4 inst_mat1;
in vec4 inst_mat2;
@ -16,6 +17,7 @@ in float inst_wind_sway;
out vec3 f_pos;
flat out vec3 f_norm;
out vec3 f_col;
out float f_ao;
out float f_light;
const float SCALE = 1.0 / 11.0;
@ -41,6 +43,7 @@ void main() {
f_norm = (inst_mat * vec4(v_norm, 0)).xyz;
f_col = srgb_to_linear(v_col) * srgb_to_linear(inst_col);
f_ao = v_ao;
// Select glowing
if (select_pos.w > 0 && select_pos.xyz == floor(sprite_pos)) {

View File

@ -61,6 +61,7 @@ pub enum Activity {
chaser: Chaser,
time: f64,
been_close: bool,
powerup: f32,
},
}

View File

@ -1,11 +1,21 @@
use vek::*;
use crate::comp::{Alignment};
use crate::{
comp::{self, Alignment, Body, Item, humanoid},
npc::{self, NPC_NAMES},
};
pub enum EntityTemplate {
Traveller,
}
pub struct EntityInfo {
pub pos: Vec3<f32>,
pub is_waypoint: bool, // Edge case, overrides everything else
pub is_giant: bool,
pub alignment: Alignment,
pub body: Body,
pub name: Option<String>,
pub main_tool: Option<Item>,
}
impl EntityInfo {
@ -15,6 +25,9 @@ impl EntityInfo {
is_waypoint: false,
is_giant: false,
alignment: Alignment::Wild,
body: Body::Humanoid(humanoid::Body::random()),
name: None,
main_tool: None,
}
}
@ -39,6 +52,37 @@ impl EntityInfo {
self.alignment = alignment;
self
}
pub fn with_body(mut self, body: Body) -> Self {
self.body = body;
self
}
pub fn with_name(mut self, name: String) -> Self {
self.name = Some(name);
self
}
pub fn with_main_tool(mut self, main_tool: Item) -> Self {
self.main_tool = Some(main_tool);
self
}
pub fn with_automatic_name(mut self) -> Self {
self.name = match &self.body {
Body::Humanoid(body) => Some(get_npc_name(&NPC_NAMES.humanoid, body.race)),
Body::QuadrupedMedium(body) => Some(get_npc_name(&NPC_NAMES.quadruped_medium, body.species)),
Body::BirdMedium(body) => Some(get_npc_name(&NPC_NAMES.bird_medium, body.species)),
Body::Critter(body) => Some(get_npc_name(&NPC_NAMES.critter, body.species)),
Body::QuadrupedSmall(body) => Some(get_npc_name(&NPC_NAMES.quadruped_small, body.species)),
_ => None,
}.map(|s| if self.is_giant {
format!("Giant {}", s)
} else {
s.to_string()
});
self
}
}
#[derive(Default)]
@ -49,3 +93,14 @@ pub struct ChunkSupplement {
impl ChunkSupplement {
pub fn add_entity(&mut self, entity: EntityInfo) { self.entities.push(entity); }
}
fn get_npc_name<
'a,
Species,
SpeciesData: for<'b> core::ops::Index<&'b Species, Output = npc::SpeciesNames>,
>(
body_data: &'a comp::BodyData<npc::BodyNames, SpeciesData>,
species: Species,
) -> &'a str {
&body_data.species[&species].generic
}

View File

@ -1,7 +1,7 @@
use crate::{
comp::{self, agent::Activity, Agent, Alignment, Controller, MountState, Ori, Scale, Pos, Stats},
comp::{self, agent::Activity, Agent, Alignment, Controller, MountState, Ori, Scale, Pos, Stats, Loadout, item::{ItemKind, tool::ToolKind}, CharacterState},
path::Chaser,
state::Time,
state::{Time, DeltaTime},
sync::UidAllocator,
terrain::TerrainGrid,
util::Dir,
@ -20,11 +20,14 @@ impl<'a> System<'a> for Sys {
type SystemData = (
Read<'a, UidAllocator>,
Read<'a, Time>,
Read<'a, DeltaTime>,
Entities<'a>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Ori>,
ReadStorage<'a, Scale>,
ReadStorage<'a, Stats>,
ReadStorage<'a, Loadout>,
ReadStorage<'a, CharacterState>,
ReadExpect<'a, TerrainGrid>,
ReadStorage<'a, Alignment>,
WriteStorage<'a, Agent>,
@ -37,11 +40,14 @@ impl<'a> System<'a> for Sys {
(
uid_allocator,
time,
dt,
entities,
positions,
orientations,
scales,
stats,
loadouts,
character_states,
terrain,
alignments,
mut agents,
@ -49,11 +55,13 @@ impl<'a> System<'a> for Sys {
mount_states,
): Self::SystemData,
) {
for (entity, pos, ori, alignment, agent, controller, mount_state) in (
for (entity, pos, ori, alignment, loadout, character_state, agent, controller, mount_state) in (
&entities,
&positions,
&orientations,
alignments.maybe(),
&loadouts,
&character_states,
&mut agents,
&mut controllers,
mount_states.maybe(),
@ -100,7 +108,7 @@ impl<'a> System<'a> for Sys {
thread_rng().gen::<f32>() - 0.5,
thread_rng().gen::<f32>() - 0.5,
) * 0.1
- *bearing * 0.01
- *bearing * 0.003
- if let Some(patrol_origin) = agent.patrol_origin {
Vec2::<f32>::from(pos.0 - patrol_origin) * 0.0002
} else {
@ -165,8 +173,24 @@ impl<'a> System<'a> for Sys {
target,
chaser,
been_close,
powerup,
..
} => {
enum Tactic {
Melee, // Attack: primary/secondary
RangedPowerup,
RangedConstant,
}
let tactic = match loadout.active_item
.as_ref()
.and_then(|ic| if let ItemKind::Tool(tool) = &ic.item.kind { Some(&tool.kind) } else { None})
{
Some(ToolKind::Bow(_)) => Tactic::RangedPowerup,
Some(ToolKind::Staff(_)) => Tactic::RangedConstant,
_ => Tactic::Melee,
};
if let (Some(tgt_pos), Some(tgt_stats), tgt_alignment) = (
positions.get(*target),
stats.get(*target),
@ -196,10 +220,35 @@ impl<'a> System<'a> for Sys {
.try_normalized()
.unwrap_or(Vec2::unit_y())
* 0.7;
inputs.primary.set_state(true);
if let Tactic::Melee = tactic {
inputs.primary.set_state(true);
} else if let Tactic::RangedPowerup = tactic {
inputs.primary.set_state(true);
} else {
inputs.move_dir = -Vec2::from(tgt_pos.0 - pos.0)
.try_normalized()
.unwrap_or(Vec2::unit_y());
}
} else if dist_sqrd < MAX_CHASE_DIST.powf(2.0)
|| (dist_sqrd < SIGHT_DIST.powf(2.0) && !*been_close)
{
if let Tactic::RangedPowerup = tactic {
if *powerup > 2.0 {
inputs.primary.set_state(false);
*powerup = 0.0;
} else {
inputs.primary.set_state(true);
*powerup += dt.0;
}
} else if let Tactic::RangedConstant = tactic {
if !character_state.is_wield() {
inputs.primary.set_state(true);
}
inputs.secondary.set_state(true);
}
if dist_sqrd < MAX_CHASE_DIST.powf(2.0) {
*been_close = true;
}
@ -257,6 +306,7 @@ impl<'a> System<'a> for Sys {
chaser: Chaser::default(),
time: time.0,
been_close: false,
powerup: 0.0,
};
}
}
@ -280,6 +330,7 @@ impl<'a> System<'a> for Sys {
chaser: Chaser::default(),
time: time.0,
been_close: false,
powerup: 0.0,
};
}
}
@ -310,6 +361,7 @@ impl<'a> System<'a> for Sys {
chaser: Chaser::default(),
time: time.0,
been_close: false,
powerup: 0.0,
};
}
}

View File

@ -3,7 +3,7 @@ use crate::{
event::{EventBus, ServerEvent},
state::DeltaTime,
sync::Uid,
terrain::{Block, TerrainGrid},
terrain::{Block, BlockKind, TerrainGrid},
vol::ReadVol,
};
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
@ -110,7 +110,7 @@ impl<'a> System<'a> for Sys {
// Neighbouring blocks iterator
let near_iter = (-hdist..hdist + 1)
.map(move |i| {
(-hdist..hdist + 1).map(move |j| (0..vdist + 1).map(move |k| (i, j, k)))
(-hdist..hdist + 1).map(move |j| (1 - BlockKind::MAX_HEIGHT.ceil() as i32..vdist + 1).map(move |k| (i, j, k)))
})
.flatten()
.flatten();
@ -154,14 +154,14 @@ impl<'a> System<'a> for Sys {
for (i, j, k) in near_iter {
let block_pos = pos.map(|e| e.floor() as i32) + Vec3::new(i, j, k);
if terrain.get(block_pos).map(hit).unwrap_or(false) {
if let Some(block) = terrain.get(block_pos).ok().copied().filter(hit) {
let player_aabb = Aabb {
min: pos + Vec3::new(-player_rad, -player_rad, 0.0),
max: pos + Vec3::new(player_rad, player_rad, player_height),
};
let block_aabb = Aabb {
min: block_pos.map(|e| e as f32),
max: block_pos.map(|e| e as f32) + 1.0,
max: block_pos.map(|e| e as f32) + Vec3::new(1.0, 1.0, block.get_height()),
};
if player_aabb.collides_with_aabb(block_aabb) {
@ -204,22 +204,24 @@ impl<'a> System<'a> for Sys {
.clone()
// Calculate the block's position in world space
.map(|(i, j, k)| pos.0.map(|e| e.floor() as i32) + Vec3::new(i, j, k))
// Calculate the AABB of the block
.map(|block_pos| {
(
block_pos,
Aabb {
min: block_pos.map(|e| e as f32),
max: block_pos.map(|e| e as f32) + 1.0,
},
)
})
// Make sure the block is actually solid
.filter(|(block_pos, _)| {
terrain
.get(*block_pos)
.map(|vox| vox.is_solid())
.unwrap_or(false)
.filter_map(|block_pos| {
if let Some(block) = terrain
.get(block_pos)
.ok()
.filter(|block| block.is_solid())
{
// Calculate block AABB
Some((
block_pos,
Aabb {
min: block_pos.map(|e| e as f32),
max: block_pos.map(|e| e as f32) + Vec3::new(1.0, 1.0, block.get_height()),
},
))
} else {
None
}
})
// Determine whether the block's AABB collides with the player's AABB
.filter(|(_, block_aabb)| block_aabb.collides_with_aabb(player_aabb))

View File

@ -205,6 +205,18 @@ impl BlockKind {
}
}
pub const MAX_HEIGHT: f32 = 3.0;
// TODO: Integrate this into `is_solid` by returning an `Option<f32>`
pub fn get_height(&self) -> f32 {
// Beware: the height *must* be <= `MAX_HEIGHT` or the collision system will not properly
// detect it!
match self {
BlockKind::LargeCactus => 2.5,
_ => 1.0,
}
}
pub fn is_collectible(&self) -> bool {
match self {
BlockKind::BlueFlower => false,

View File

@ -116,88 +116,92 @@ impl<'a> System<'a> for Sys {
&body_data.species[&species].generic
}
const SPAWN_NPCS: &'static [fn() -> (
String,
comp::Body,
Option<comp::Item>,
comp::Alignment,
)] = &[
(|| {
let body = comp::humanoid::Body::random();
(
format!(
"{} Traveler",
get_npc_name(&NPC_NAMES.humanoid, body.race)
),
comp::Body::Humanoid(body),
Some(assets::load_expect_cloned(
"common.items.weapons.starter_axe",
)),
comp::Alignment::Npc,
)
}) as _,
(|| {
let body = comp::humanoid::Body::random();
(
format!("{} Bandit", get_npc_name(&NPC_NAMES.humanoid, body.race)),
comp::Body::Humanoid(body),
Some(assets::load_expect_cloned(
"common.items.weapons.short_sword_0",
)),
comp::Alignment::Enemy,
)
}) as _,
(|| {
let body = comp::quadruped_medium::Body::random();
(
get_npc_name(&NPC_NAMES.quadruped_medium, body.species).into(),
comp::Body::QuadrupedMedium(body),
None,
comp::Alignment::Enemy,
)
}) as _,
(|| {
let body = comp::bird_medium::Body::random();
(
get_npc_name(&NPC_NAMES.bird_medium, body.species).into(),
comp::Body::BirdMedium(body),
None,
comp::Alignment::Wild,
)
}) as _,
(|| {
let body = comp::critter::Body::random();
(
get_npc_name(&NPC_NAMES.critter, body.species).into(),
comp::Body::Critter(body),
None,
comp::Alignment::Wild,
)
}) as _,
(|| {
let body = comp::quadruped_small::Body::random();
(
get_npc_name(&NPC_NAMES.quadruped_small, body.species).into(),
comp::Body::QuadrupedSmall(body),
None,
comp::Alignment::Wild,
)
}),
];
let (name, mut body, main, mut alignment) = SPAWN_NPCS
.choose(&mut rand::thread_rng())
.expect("SPAWN_NPCS is nonempty")(
);
// const SPAWN_NPCS: &'static [fn() -> (
// String,
// comp::Body,
// Option<comp::Item>,
// comp::Alignment,
// )] = &[
// (|| {
// let body = comp::humanoid::Body::random();
// (
// format!(
// "{} Traveler",
// get_npc_name(&NPC_NAMES.humanoid, body.race)
// ),
// comp::Body::Humanoid(body),
// Some(assets::load_expect_cloned(
// "common.items.weapons.starter_axe",
// )),
// comp::Alignment::Npc,
// )
// }) as _,
// (|| {
// let body = comp::humanoid::Body::random();
// (
// format!("{} Bandit", get_npc_name(&NPC_NAMES.humanoid, body.race)),
// comp::Body::Humanoid(body),
// Some(assets::load_expect_cloned(
// "common.items.weapons.short_sword_0",
// )),
// comp::Alignment::Enemy,
// )
// }) as _,
// (|| {
// let body = comp::quadruped_medium::Body::random();
// (
// get_npc_name(&NPC_NAMES.quadruped_medium, body.species).into(),
// comp::Body::QuadrupedMedium(body),
// None,
// comp::Alignment::Enemy,
// )
// }) as _,
// (|| {
// let body = comp::bird_medium::Body::random();
// (
// get_npc_name(&NPC_NAMES.bird_medium, body.species).into(),
// comp::Body::BirdMedium(body),
// None,
// comp::Alignment::Wild,
// )
// }) as _,
// (|| {
// let body = comp::critter::Body::random();
// (
// get_npc_name(&NPC_NAMES.critter, body.species).into(),
// comp::Body::Critter(body),
// None,
// comp::Alignment::Wild,
// )
// }) as _,
// (|| {
// let body = comp::quadruped_small::Body::random();
// (
// get_npc_name(&NPC_NAMES.quadruped_small, body.species).into(),
// comp::Body::QuadrupedSmall(body),
// None,
// comp::Alignment::Wild,
// )
// }),
// ];
// let (name, mut body, main, mut alignment) = SPAWN_NPCS
// .choose(&mut rand::thread_rng())
// .expect("SPAWN_NPCS is nonempty")(
// );
let mut body = entity.body;
let mut name = entity.name.unwrap_or("Unnamed".to_string());
let alignment = entity.alignment;
let main_tool = entity.main_tool;
let mut stats = comp::Stats::new(name, body);
let alignment = entity.alignment;
let active_item =
if let Some(item::ItemKind::Tool(tool)) = main.as_ref().map(|i| &i.kind) {
if let Some(item::ItemKind::Tool(tool)) = main_tool.as_ref().map(|i| &i.kind) {
let mut abilities = tool.get_abilities();
let mut ability_drain = abilities.drain(..);
main.map(|item| comp::ItemConfig {
main_tool.map(|item| comp::ItemConfig {
item,
ability1: ability_drain.next(),
ability2: ability_drain.next(),

View File

@ -88,9 +88,8 @@ impl Meshable<SpritePipeline, SpritePipeline> for Segment {
SpriteVertex::new(
origin,
norm,
linear_to_srgb(
srgb_to_linear(col) * light.min(ao.powf(0.5) * 0.75 + 0.25),
),
linear_to_srgb(srgb_to_linear(col) * light),
ao,
)
},
&{

View File

@ -14,6 +14,7 @@ gfx_defines! {
pos: [f32; 3] = "v_pos",
norm: [f32; 3] = "v_norm",
col: [f32; 3] = "v_col",
ao: f32 = "v_ao",
}
vertex Instance {
@ -41,11 +42,12 @@ gfx_defines! {
}
impl Vertex {
pub fn new(pos: Vec3<f32>, norm: Vec3<f32>, col: Rgb<f32>) -> Self {
pub fn new(pos: Vec3<f32>, norm: Vec3<f32>, col: Rgb<f32>, ao: f32) -> Self {
Self {
pos: pos.into_array(),
col: col.into_array(),
norm: norm.into_array(),
ao,
}
}
}

View File

@ -21,7 +21,7 @@ use crate::{
};
use common::{
generation::{ChunkSupplement, EntityInfo},
comp::{Alignment},
comp::{self, humanoid, quadruped_medium, bird_medium, critter, quadruped_small},
terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
vol::{ReadVol, RectVolSize, Vox, WriteVol},
};
@ -150,25 +150,29 @@ impl World {
}
}
let sample_get = |offs| {
zcache_grid
.get(grid_border + offs)
.map(Option::as_ref)
.flatten()
.map(|zc| &zc.sample)
};
let mut rng = rand::thread_rng();
// Apply site generation
sim_chunk.sites.iter().for_each(|site| {
site.apply_to(
chunk_wpos2d,
|offs| {
zcache_grid
.get(grid_border + offs)
.map(Option::as_ref)
.flatten()
.map(|zc| &zc.sample)
},
sample_get,
&mut chunk,
)
});
let gen_entity_pos = || {
let lpos2d = TerrainChunkSize::RECT_SIZE
.map(|sz| rand::thread_rng().gen::<u32>().rem_euclid(sz));
let mut lpos = Vec3::new(lpos2d.x as i32, lpos2d.y as i32, 0);
.map(|sz| rand::thread_rng().gen::<u32>().rem_euclid(sz) as i32);
let mut lpos = Vec3::new(lpos2d.x, lpos2d.y, sample_get(lpos2d).map(|s| s.alt as i32 - 32).unwrap_or(0));
while chunk.get(lpos).map(|vox| !vox.is_empty()).unwrap_or(false) {
lpos.z += 1;
@ -177,8 +181,6 @@ impl World {
(Vec3::from(chunk_wpos2d) + lpos).map(|e: i32| e as f32) + 0.5
};
let mut rng = rand::thread_rng();
const SPAWN_RATE: f32 = 0.1;
const BOSS_RATE: f32 = 0.03;
let mut supplement = ChunkSupplement {
@ -187,8 +189,15 @@ impl World {
&& !sim_chunk.is_underwater()
{
let entity = EntityInfo::at(gen_entity_pos())
.with_alignment(Alignment::Wild)
.do_if(rng.gen(), |e| e.into_giant());
.with_alignment(comp::Alignment::Wild)
.do_if(rng.gen_range(0, 8) == 0, |e| e.into_giant())
.with_body(match rng.gen_range(0, 4) {
0 => comp::Body::QuadrupedMedium(quadruped_medium::Body::random()),
1 => comp::Body::BirdMedium(bird_medium::Body::random()),
2 => comp::Body::Critter(critter::Body::random()),
_ => comp::Body::QuadrupedSmall(quadruped_small::Body::random()),
})
.with_automatic_name();
vec![entity]
} else {
@ -204,14 +213,9 @@ impl World {
// Apply site supplementary information
sim_chunk.sites.iter().for_each(|site| {
site.apply_supplement(
&mut rng,
chunk_wpos2d,
|offs| {
zcache_grid
.get(grid_border + offs)
.map(Option::as_ref)
.flatten()
.map(|zc| &zc.sample)
},
sample_get,
&mut supplement,
)
});

View File

@ -6,9 +6,10 @@ use crate::{
util::{attempt, Grid, RandomField, Sampler, StructureGen2d},
};
use common::{
astar::Astar,
comp::Alignment,
assets,
comp,
generation::{ChunkSupplement, EntityInfo},
astar::Astar,
path::Path,
spiral::Spiral2d,
store::{Id, Store},
@ -118,6 +119,7 @@ impl Dungeon {
pub fn apply_supplement<'a>(
&'a self,
rng: &mut impl Rng,
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
supplement: &mut ChunkSupplement,
@ -132,7 +134,7 @@ impl Dungeon {
for floor in &self.floors {
z -= floor.total_depth();
let origin = Vec3::new(self.origin.x, self.origin.y, z);
floor.apply_supplement(area, origin, supplement);
floor.apply_supplement(rng, area, origin, supplement);
}
}
}
@ -182,6 +184,7 @@ pub struct Room {
seed: u32,
enemies: bool,
loot_density: f32,
enemy_density: f32,
area: Rect<i32, i32>,
}
@ -242,10 +245,10 @@ impl Floor {
}
fn create_rooms(&mut self, ctx: &mut GenCtx<impl Rng>, level: i32, n: usize) {
let dim_limits = (3, 8);
let dim_limits = (3, 6);
for _ in 0..n {
let area = match attempt(30, || {
let area = match attempt(64, || {
let sz = Vec2::<i32>::zero().map(|_| ctx.rng.gen_range(dim_limits.0, dim_limits.1));
let pos = FLOOR_SIZE.map2(sz, |floor_sz, room_sz| {
ctx.rng.gen_range(0, floor_sz + 1 - room_sz)
@ -269,7 +272,8 @@ impl Floor {
let room = self.rooms.insert(Room {
seed: ctx.rng.gen(),
enemies: ctx.rng.gen(),
loot_density: level as f32 * 0.00025,
loot_density: 0.00005 + level as f32 * 0.00015,
enemy_density: 0.0005 + level as f32 * 0.00015,
area,
});
@ -332,6 +336,7 @@ impl Floor {
pub fn apply_supplement(
&self,
rng: &mut impl Rng,
area: Aabr<i32>,
origin: Vec3<i32>,
supplement: &mut ChunkSupplement,
@ -354,17 +359,39 @@ impl Floor {
let tile_pos = Vec2::new(x, y);
if let Some(Tile::Room(room)) = self.tiles.get(tile_pos) {
let room = &self.rooms[*room];
if room.enemies && tile_pos.x % 4 == 0 && tile_pos.y % 4 == 0 {
// Bad
let entity = EntityInfo::at(
(origin
+ Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE
+ TILE_SIZE / 2)
.map(|e| e as f32),
)
.into_giant()
.with_alignment(Alignment::Enemy);
supplement.add_entity(entity);
for x in 0..TILE_SIZE {
for y in 0..TILE_SIZE {
let pos = tile_pos * TILE_SIZE + Vec2::new(x, y);
if room.enemies && (pos.x + pos.y * TILE_SIZE * FLOOR_SIZE.x).rem_euclid(room.enemy_density.recip() as i32) == 0 {
// Bad
let entity = EntityInfo::at(
(origin
+ Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE
+ TILE_SIZE / 2)
.map(|e| e as f32)
// Randomly displace them a little
+ Vec3::<u32>::iota()
.map(|e| (RandomField::new(room.seed.wrapping_add(10 + e)).get(Vec3::from(tile_pos)) % 32) as i32 - 16)
.map(|e| e as f32 / 16.0),
)
.do_if(RandomField::new(room.seed.wrapping_add(1)).chance(Vec3::from(tile_pos), 0.2), |e| e.into_giant())
.with_alignment(comp::Alignment::Enemy)
.with_body(comp::Body::Humanoid(comp::humanoid::Body::random()))
.with_automatic_name()
.with_main_tool(assets::load_expect_cloned(match rng.gen_range(0, 6) {
0 => "common.items.weapons.starter_axe",
1 => "common.items.weapons.starter_sword",
2 => "common.items.weapons.short_sword_0",
3 => "common.items.weapons.hammer_1",
4 => "common.items.weapons.starter_staff",
_ => "common.items.weapons.starter_bow",
}));
supplement.add_entity(entity);
}
}
}
}
}

View File

@ -13,6 +13,7 @@ use common::{
terrain::Block,
vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol},
};
use rand::Rng;
use std::{fmt, sync::Arc};
use vek::*;
@ -97,15 +98,16 @@ impl Site {
pub fn apply_supplement<'a>(
&'a self,
rng: &mut impl Rng,
wpos2d: Vec2<i32>,
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
supplement: &mut ChunkSupplement,
) {
match self {
Site::Settlement(settlement) => {
settlement.apply_supplement(wpos2d, get_column, supplement)
settlement.apply_supplement(rng, wpos2d, get_column, supplement)
},
Site::Dungeon(dungeon) => dungeon.apply_supplement(wpos2d, get_column, supplement),
Site::Dungeon(dungeon) => dungeon.apply_supplement(rng, wpos2d, get_column, supplement),
}
}
}

View File

@ -8,8 +8,9 @@ use crate::{
util::{Grid, RandomField, Sampler, StructureGen2d},
};
use common::{
comp::{self, humanoid, quadruped_medium, bird_medium, critter, quadruped_small},
generation::{ChunkSupplement, EntityInfo},
astar::Astar,
generation::ChunkSupplement,
path::Path,
spiral::Spiral2d,
store::{Id, Store},
@ -105,6 +106,7 @@ impl Structure {
}
pub struct Settlement {
seed: u32,
origin: Vec2<i32>,
land: Land,
farms: Store<Farm>,
@ -130,6 +132,7 @@ impl Settlement {
pub fn generate(wpos: Vec2<i32>, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self {
let mut ctx = GenCtx { sim, rng };
let mut this = Self {
seed: ctx.rng.gen(),
origin: wpos,
land: Land::new(ctx.rng),
farms: Store::default(),
@ -480,8 +483,6 @@ impl Settlement {
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
) {
let rand_field = RandomField::new(0);
for y in 0..vol.size_xy().y as i32 {
for x in 0..vol.size_xy().x as i32 {
let offs = Vec2::new(x, y);
@ -659,7 +660,7 @@ impl Settlement {
let color = Lerp::lerp(
Rgb::new(130i32, 100, 0),
Rgb::new(90, 70, 50),
(rand_field.get(wpos2d.into()) % 256) as f32 / 256.0,
(RandomField::new(0).get(wpos2d.into()) % 256) as f32 / 256.0,
)
.map(|e| (e % 256) as u8);
@ -738,11 +739,69 @@ impl Settlement {
pub fn apply_supplement<'a>(
&'a self,
rng: &mut impl Rng,
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
supplement: &mut ChunkSupplement,
) {
// TODO
for y in 0..TerrainChunkSize::RECT_SIZE.y as i32 {
for x in 0..TerrainChunkSize::RECT_SIZE.x as i32 {
let offs = Vec2::new(x, y);
let wpos2d = wpos2d + offs;
let rpos = wpos2d - self.origin;
// Sample terrain
let col_sample = if let Some(col_sample) = get_column(offs) {
col_sample
} else {
continue;
};
let surface_z = col_sample.riverless_alt.floor() as i32;
let sample = self.land.get_at_block(rpos);
let entity_wpos = Vec3::new(wpos2d.x as f32, wpos2d.y as f32, col_sample.alt + 3.0);
if matches!(sample.plot, Some(Plot::Town)) &&
RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (32.0 * 32.0))
{
let entity = EntityInfo::at(entity_wpos)
.with_alignment(comp::Alignment::Npc)
.with_body(match rng.gen_range(0, 4) {
0 => {
let species = match rng.gen_range(0, 3) {
0 => quadruped_small::Species::Pig,
1 => quadruped_small::Species::Sheep,
_ => quadruped_small::Species::Cat,
};
comp::Body::QuadrupedSmall(quadruped_small::Body::random_with(
rng,
&species,
))
},
1 => {
let species = match rng.gen_range(0, 4) {
0 => bird_medium::Species::Duck,
1 => bird_medium::Species::Chicken,
2 => bird_medium::Species::Goose,
_ => bird_medium::Species::Peacock,
};
comp::Body::BirdMedium(bird_medium::Body::random_with(
rng,
&species,
))
},
_ => comp::Body::Humanoid(humanoid::Body::random()),
})
.with_automatic_name();
supplement.add_entity(entity);
}
}
}
}
pub fn get_color(&self, pos: Vec2<i32>) -> Option<Rgb<u8>> {