mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 14:31:30 +11:00
Complete the cow sphere™
This commit is contained in:
parent
1570c95ac8
commit
f875ed07ce
|
@ -68,7 +68,7 @@ impl Config for Game {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(&self, _server: &Server, mut worlds: WorldsMut) {
|
fn init(&self, _server: &Server, mut worlds: WorldsMut) {
|
||||||
let world_id = worlds.create(DimensionId::default());
|
let world_id = worlds.create(DimensionId::default()).0;
|
||||||
let mut world = worlds.get_mut(world_id).unwrap();
|
let mut world = worlds.get_mut(world_id).unwrap();
|
||||||
|
|
||||||
let size = 5;
|
let size = 5;
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
|
use std::f64::consts::TAU;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use valence::block::BlockState;
|
|
||||||
use valence::client::GameMode;
|
use valence::client::GameMode;
|
||||||
use valence::config::{Config, ServerListPing};
|
use valence::config::{Config, ServerListPing};
|
||||||
use valence::text::Color;
|
use valence::text::Color;
|
||||||
|
use valence::util::to_yaw_and_pitch;
|
||||||
use valence::{
|
use valence::{
|
||||||
async_trait, ChunkPos, ClientMut, DimensionId, EntityType, Server, ShutdownResult, Text,
|
async_trait, ClientMut, DimensionId, EntityId, EntityType, Server, ShutdownResult, Text,
|
||||||
TextFormat, WorldId, WorldsMut,
|
TextFormat, WorldId, WorldsMut,
|
||||||
};
|
};
|
||||||
|
use vek::{Mat3, Vec3};
|
||||||
|
|
||||||
pub fn main() -> ShutdownResult {
|
pub fn main() -> ShutdownResult {
|
||||||
env_logger::Builder::new()
|
env_logger::Builder::new()
|
||||||
|
@ -19,11 +22,13 @@ pub fn main() -> ShutdownResult {
|
||||||
|
|
||||||
valence::start_server(Game {
|
valence::start_server(Game {
|
||||||
player_count: AtomicUsize::new(0),
|
player_count: AtomicUsize::new(0),
|
||||||
|
cows: Mutex::new(Vec::new()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Game {
|
struct Game {
|
||||||
player_count: AtomicUsize,
|
player_count: AtomicUsize,
|
||||||
|
cows: Mutex<Vec<EntityId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_PLAYERS: usize = 10;
|
const MAX_PLAYERS: usize = 10;
|
||||||
|
@ -68,8 +73,7 @@ impl Config for Game {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(&self, _server: &Server, mut worlds: WorldsMut) {
|
fn init(&self, _server: &Server, mut worlds: WorldsMut) {
|
||||||
let world_id = worlds.create(DimensionId::default());
|
let mut world = worlds.create(DimensionId::default()).1;
|
||||||
let mut world = worlds.get_mut(world_id).unwrap();
|
|
||||||
world.meta.set_flat(true);
|
world.meta.set_flat(true);
|
||||||
|
|
||||||
let size = 5;
|
let size = 5;
|
||||||
|
@ -79,13 +83,11 @@ impl Config for Game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let entity_id = world.entities.create();
|
self.cows.lock().unwrap().extend((0..200).map(|_| {
|
||||||
let mut entity = world.entities.get_mut(entity_id).unwrap();
|
let (id, mut e) = world.entities.create();
|
||||||
|
e.set_type(EntityType::Cow);
|
||||||
entity.set_type(EntityType::Cow);
|
id
|
||||||
entity.set_position([0.0, 100.0, 0.0]);
|
}));
|
||||||
//entity.set_yaw(30.0);
|
|
||||||
//entity.set_pitch(0.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&self, server: &Server, mut worlds: WorldsMut) {
|
fn update(&self, server: &Server, mut worlds: WorldsMut) {
|
||||||
|
@ -105,15 +107,54 @@ impl Config for Game {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (_, mut e) in world.entities.iter_mut() {
|
let time = server.current_tick() as f64 / server.tick_rate() as f64;
|
||||||
let time = server.current_tick() as f64 / server.tick_rate() as f64;
|
|
||||||
|
|
||||||
if e.typ() == EntityType::Cow {
|
let rot = Mat3::rotation_x(time * TAU * 0.1)
|
||||||
e.set_position(e.position() + [0.0, 0.0, 0.02]);
|
.rotated_y(time * TAU * 0.2)
|
||||||
let yaw = (time % 1.0 * 360.0) as f32;
|
.rotated_z(time * TAU * 0.3);
|
||||||
e.set_yaw(yaw);
|
|
||||||
e.set_head_yaw(yaw);
|
let cows = self.cows.lock().unwrap();
|
||||||
}
|
let cow_count = cows.len();
|
||||||
|
|
||||||
|
let radius = 6.0 + ((time * TAU / 2.5).sin() + 1.0) / 2.0 * 10.0;
|
||||||
|
|
||||||
|
// TODO: use eye position.
|
||||||
|
let player_pos = world
|
||||||
|
.clients
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.map(|c| c.1.position())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
for (cow_id, p) in cows.iter().cloned().zip(fibonacci_spiral(cow_count)) {
|
||||||
|
let mut cow = world.entities.get_mut(cow_id).expect("missing cow");
|
||||||
|
let rotated = p * rot;
|
||||||
|
let transformed = rotated * radius + [0.0, 100.0, 0.0];
|
||||||
|
|
||||||
|
let yaw = f32::atan2(rotated.z as f32, rotated.x as f32).to_degrees() - 90.0;
|
||||||
|
let (looking_yaw, looking_pitch) =
|
||||||
|
to_yaw_and_pitch((player_pos - transformed).normalized());
|
||||||
|
|
||||||
|
cow.set_position(transformed);
|
||||||
|
cow.set_yaw(yaw);
|
||||||
|
cow.set_pitch(looking_pitch);
|
||||||
|
cow.set_head_yaw(looking_yaw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Distributes N points on the surface of a unit sphere.
|
||||||
|
fn fibonacci_spiral(n: usize) -> impl Iterator<Item = Vec3<f64>> {
|
||||||
|
(0..n).map(move |i| {
|
||||||
|
let golden_ratio = (1.0 + 5_f64.sqrt()) / 2.0;
|
||||||
|
|
||||||
|
// Map to unit square
|
||||||
|
let x = i as f64 / golden_ratio % 1.0;
|
||||||
|
let y = i as f64 / n as f64;
|
||||||
|
|
||||||
|
// Map from unit square to unit sphere.
|
||||||
|
let theta = x * TAU;
|
||||||
|
let phi = (1.0 - 2.0 * y).acos();
|
||||||
|
Vec3::new(theta.cos() * phi.sin(), theta.sin() * phi.sin(), phi.cos())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -77,8 +77,9 @@ impl<'a> ClientsMut<'a> {
|
||||||
ClientsMut(self.0)
|
ClientsMut(self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn create(&mut self, client: Client) -> ClientId {
|
pub(crate) fn create(&mut self, client: Client) -> (ClientId, ClientMut) {
|
||||||
ClientId(self.0.sm.insert(client))
|
let (id, client) = self.0.sm.insert(client);
|
||||||
|
(ClientId(id), ClientMut(client))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&mut self, client: ClientId) -> bool {
|
pub fn delete(&mut self, client: ClientId) -> bool {
|
||||||
|
|
|
@ -98,7 +98,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
/// An unspecified value is returned that should be adequate in most
|
/// An unspecified value is returned that should be adequate in most
|
||||||
/// situations.
|
/// situations.
|
||||||
fn outgoing_packet_capacity(&self) -> usize {
|
fn outgoing_packet_capacity(&self) -> usize {
|
||||||
128
|
512
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called once at startup to get a handle to the tokio runtime the server
|
/// Called once at startup to get a handle to the tokio runtime the server
|
||||||
|
|
|
@ -81,13 +81,9 @@ impl<'a> EntitiesMut<'a> {
|
||||||
///
|
///
|
||||||
/// To actually see the new entity, set its position to somewhere nearby and
|
/// To actually see the new entity, set its position to somewhere nearby and
|
||||||
/// [set its type](EntityData::set_type) to something visible.
|
/// [set its type](EntityData::set_type) to something visible.
|
||||||
pub fn create(&mut self) -> EntityId {
|
pub fn create(&mut self) -> (EntityId, EntityMut) {
|
||||||
loop {
|
self.create_with_uuid(Uuid::from_bytes(rand::random()))
|
||||||
let uuid = Uuid::from_bytes(rand::random());
|
.expect("UUID collision")
|
||||||
if let Some(entity) = self.create_with_uuid(uuid) {
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like [`create`](Entities::create), but requires specifying the new
|
/// Like [`create`](Entities::create), but requires specifying the new
|
||||||
|
@ -95,11 +91,11 @@ impl<'a> EntitiesMut<'a> {
|
||||||
///
|
///
|
||||||
/// The provided UUID must not conflict with an existing entity UUID in this
|
/// The provided UUID must not conflict with an existing entity UUID in this
|
||||||
/// world. If it does, `None` is returned and the entity is not spawned.
|
/// world. If it does, `None` is returned and the entity is not spawned.
|
||||||
pub fn create_with_uuid(&mut self, uuid: Uuid) -> Option<EntityId> {
|
pub fn create_with_uuid(&mut self, uuid: Uuid) -> Option<(EntityId, EntityMut)> {
|
||||||
match self.0.uuid_to_entity.entry(uuid) {
|
match self.0.uuid_to_entity.entry(uuid) {
|
||||||
Entry::Occupied(_) => None,
|
Entry::Occupied(_) => None,
|
||||||
Entry::Vacant(ve) => {
|
Entry::Vacant(ve) => {
|
||||||
let entity = EntityId(self.0.sm.insert(Entity {
|
let (id, entity) = self.0.sm.insert(Entity {
|
||||||
flags: EntityFlags(0),
|
flags: EntityFlags(0),
|
||||||
meta: EntityMeta::new(EntityType::Marker),
|
meta: EntityMeta::new(EntityType::Marker),
|
||||||
new_position: Vec3::default(),
|
new_position: Vec3::default(),
|
||||||
|
@ -107,16 +103,15 @@ impl<'a> EntitiesMut<'a> {
|
||||||
yaw: 0.0,
|
yaw: 0.0,
|
||||||
pitch: 0.0,
|
pitch: 0.0,
|
||||||
head_yaw: 0.0,
|
head_yaw: 0.0,
|
||||||
head_pitch: 0.0,
|
|
||||||
velocity: Vec3::default(),
|
velocity: Vec3::default(),
|
||||||
uuid,
|
uuid,
|
||||||
}));
|
});
|
||||||
|
|
||||||
ve.insert(entity);
|
ve.insert(EntityId(id));
|
||||||
|
|
||||||
// TODO: insert into partition.
|
// TODO: insert into partition.
|
||||||
|
|
||||||
Some(entity)
|
Some((EntityId(id), EntityMut(entity)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,7 +183,6 @@ pub struct Entity {
|
||||||
yaw: f32,
|
yaw: f32,
|
||||||
pitch: f32,
|
pitch: f32,
|
||||||
head_yaw: f32,
|
head_yaw: f32,
|
||||||
head_pitch: f32,
|
|
||||||
velocity: Vec3<f32>,
|
velocity: Vec3<f32>,
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,11 +76,11 @@ impl<T> SlotMap<T> {
|
||||||
self.count as usize
|
self.count as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, val: T) -> Key {
|
pub fn insert(&mut self, val: T) -> (Key, &mut T) {
|
||||||
self.insert_with(|_| val)
|
self.insert_with(|_| val)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_with(&mut self, f: impl FnOnce(Key) -> T) -> Key {
|
pub fn insert_with(&mut self, f: impl FnOnce(Key) -> T) -> (Key, &mut T) {
|
||||||
assert!(self.count < u32::MAX, "SlotMap: too many items inserted");
|
assert!(self.count < u32::MAX, "SlotMap: too many items inserted");
|
||||||
|
|
||||||
if self.next_free_head == self.slots.len() as u32 {
|
if self.next_free_head == self.slots.len() as u32 {
|
||||||
|
@ -93,7 +93,12 @@ impl<T> SlotMap<T> {
|
||||||
value: f(key),
|
value: f(key),
|
||||||
version: key.version(),
|
version: key.version(),
|
||||||
});
|
});
|
||||||
key
|
|
||||||
|
let value = match self.slots.last_mut() {
|
||||||
|
Some(Slot::Occupied { value, .. }) => value,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
(key, value)
|
||||||
} else {
|
} else {
|
||||||
let slot = &mut self.slots[self.next_free_head as usize];
|
let slot = &mut self.slots[self.next_free_head as usize];
|
||||||
|
|
||||||
|
@ -109,9 +114,15 @@ impl<T> SlotMap<T> {
|
||||||
version: key.version(),
|
version: key.version(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let value = match slot {
|
||||||
|
Slot::Occupied { value, .. } => value,
|
||||||
|
Slot::Free { .. } => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
self.next_free_head = next_free;
|
self.next_free_head = next_free;
|
||||||
self.count += 1;
|
self.count += 1;
|
||||||
key
|
|
||||||
|
(key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,14 +245,14 @@ mod tests {
|
||||||
fn insert_remove() {
|
fn insert_remove() {
|
||||||
let mut sm = SlotMap::new();
|
let mut sm = SlotMap::new();
|
||||||
|
|
||||||
let k0 = sm.insert(10);
|
let k0 = sm.insert(10).0;
|
||||||
let k1 = sm.insert(20);
|
let k1 = sm.insert(20).0;
|
||||||
let k2 = sm.insert(30);
|
let k2 = sm.insert(30).0;
|
||||||
|
|
||||||
assert_eq!(sm.remove(k1), Some(20));
|
assert_eq!(sm.remove(k1), Some(20));
|
||||||
assert_eq!(sm.get(k1), None);
|
assert_eq!(sm.get(k1), None);
|
||||||
assert_eq!(sm.get(k2), Some(&30));
|
assert_eq!(sm.get(k2), Some(&30));
|
||||||
let k3 = sm.insert(40);
|
let k3 = sm.insert(40).0;
|
||||||
assert_eq!(sm.get(k0), Some(&10));
|
assert_eq!(sm.get(k0), Some(&10));
|
||||||
assert_eq!(sm.get_mut(k3), Some(&mut 40));
|
assert_eq!(sm.get_mut(k3), Some(&mut 40));
|
||||||
assert_eq!(sm.remove(k0), Some(10));
|
assert_eq!(sm.remove(k0), Some(10));
|
||||||
|
@ -254,9 +265,9 @@ mod tests {
|
||||||
fn retain() {
|
fn retain() {
|
||||||
let mut sm = SlotMap::new();
|
let mut sm = SlotMap::new();
|
||||||
|
|
||||||
let k0 = sm.insert(10);
|
let k0 = sm.insert(10).0;
|
||||||
let k1 = sm.insert(20);
|
let k1 = sm.insert(20).0;
|
||||||
let k2 = sm.insert(30);
|
let k2 = sm.insert(30).0;
|
||||||
|
|
||||||
sm.retain(|k, _| k == k1);
|
sm.retain(|k, _| k == k1);
|
||||||
|
|
||||||
|
|
18
src/util.rs
18
src/util.rs
|
@ -53,3 +53,21 @@ where
|
||||||
|
|
||||||
aabb
|
aabb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Takes a normalized direction vector and returns a (yaw, pitch) tuple in degrees.
|
||||||
|
///
|
||||||
|
/// This function is the inverse of [`from_yaw_and_pitch`].
|
||||||
|
pub fn to_yaw_and_pitch(d: Vec3<f64>) -> (f32, f32) {
|
||||||
|
debug_assert!(d.is_normalized(), "the given vector should be normalized");
|
||||||
|
|
||||||
|
let yaw = f32::atan2(d.z as f32, d.x as f32).to_degrees() - 90.0;
|
||||||
|
let pitch = -(d.y as f32).asin().to_degrees();
|
||||||
|
(yaw, pitch)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes yaw and pitch angles (in degrees) and returns a normalized direction vector.
|
||||||
|
///
|
||||||
|
/// This function is the inverse of [`to_yaw_and_pitch`].
|
||||||
|
pub fn from_yaw_and_pitch(yaw: f32, pitch: f32) -> Vec3<f64> {
|
||||||
|
todo!()
|
||||||
|
}
|
|
@ -55,8 +55,8 @@ impl<'a> WorldsMut<'a> {
|
||||||
WorldsMut(self.0)
|
WorldsMut(self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create(&mut self, dim: DimensionId) -> WorldId {
|
pub fn create(&mut self, dim: DimensionId) -> (WorldId, WorldMut) {
|
||||||
WorldId(self.0.sm.insert(World {
|
let (id, world) = self.0.sm.insert(World {
|
||||||
clients: Clients::new(),
|
clients: Clients::new(),
|
||||||
entities: Entities::new(),
|
entities: Entities::new(),
|
||||||
chunks: Chunks::new(
|
chunks: Chunks::new(
|
||||||
|
@ -67,7 +67,9 @@ impl<'a> WorldsMut<'a> {
|
||||||
dimension: dim,
|
dimension: dim,
|
||||||
is_flat: false,
|
is_flat: false,
|
||||||
},
|
},
|
||||||
}))
|
});
|
||||||
|
|
||||||
|
(WorldId(id), WorldMut::new(world))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes a world from the server. Any [`WorldId`] referring to the
|
/// Deletes a world from the server. Any [`WorldId`] referring to the
|
||||||
|
|
Loading…
Reference in a new issue