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) {
|
||||
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 size = 5;
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
use std::f64::consts::TAU;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use log::LevelFilter;
|
||||
use valence::block::BlockState;
|
||||
use valence::client::GameMode;
|
||||
use valence::config::{Config, ServerListPing};
|
||||
use valence::text::Color;
|
||||
use valence::util::to_yaw_and_pitch;
|
||||
use valence::{
|
||||
async_trait, ChunkPos, ClientMut, DimensionId, EntityType, Server, ShutdownResult, Text,
|
||||
async_trait, ClientMut, DimensionId, EntityId, EntityType, Server, ShutdownResult, Text,
|
||||
TextFormat, WorldId, WorldsMut,
|
||||
};
|
||||
use vek::{Mat3, Vec3};
|
||||
|
||||
pub fn main() -> ShutdownResult {
|
||||
env_logger::Builder::new()
|
||||
|
@ -19,11 +22,13 @@ pub fn main() -> ShutdownResult {
|
|||
|
||||
valence::start_server(Game {
|
||||
player_count: AtomicUsize::new(0),
|
||||
cows: Mutex::new(Vec::new()),
|
||||
})
|
||||
}
|
||||
|
||||
struct Game {
|
||||
player_count: AtomicUsize,
|
||||
cows: Mutex<Vec<EntityId>>,
|
||||
}
|
||||
|
||||
const MAX_PLAYERS: usize = 10;
|
||||
|
@ -68,8 +73,7 @@ impl Config for Game {
|
|||
}
|
||||
|
||||
fn init(&self, _server: &Server, mut worlds: WorldsMut) {
|
||||
let world_id = worlds.create(DimensionId::default());
|
||||
let mut world = worlds.get_mut(world_id).unwrap();
|
||||
let mut world = worlds.create(DimensionId::default()).1;
|
||||
world.meta.set_flat(true);
|
||||
|
||||
let size = 5;
|
||||
|
@ -79,13 +83,11 @@ impl Config for Game {
|
|||
}
|
||||
}
|
||||
|
||||
let entity_id = world.entities.create();
|
||||
let mut entity = world.entities.get_mut(entity_id).unwrap();
|
||||
|
||||
entity.set_type(EntityType::Cow);
|
||||
entity.set_position([0.0, 100.0, 0.0]);
|
||||
//entity.set_yaw(30.0);
|
||||
//entity.set_pitch(0.0);
|
||||
self.cows.lock().unwrap().extend((0..200).map(|_| {
|
||||
let (id, mut e) = world.entities.create();
|
||||
e.set_type(EntityType::Cow);
|
||||
id
|
||||
}));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if e.typ() == EntityType::Cow {
|
||||
e.set_position(e.position() + [0.0, 0.0, 0.02]);
|
||||
let yaw = (time % 1.0 * 360.0) as f32;
|
||||
e.set_yaw(yaw);
|
||||
e.set_head_yaw(yaw);
|
||||
let rot = Mat3::rotation_x(time * TAU * 0.1)
|
||||
.rotated_y(time * TAU * 0.2)
|
||||
.rotated_z(time * TAU * 0.3);
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub(crate) fn create(&mut self, client: Client) -> ClientId {
|
||||
ClientId(self.0.sm.insert(client))
|
||||
pub(crate) fn create(&mut self, client: Client) -> (ClientId, ClientMut) {
|
||||
let (id, client) = self.0.sm.insert(client);
|
||||
(ClientId(id), ClientMut(client))
|
||||
}
|
||||
|
||||
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
|
||||
/// situations.
|
||||
fn outgoing_packet_capacity(&self) -> usize {
|
||||
128
|
||||
512
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// [set its type](EntityData::set_type) to something visible.
|
||||
pub fn create(&mut self) -> EntityId {
|
||||
loop {
|
||||
let uuid = Uuid::from_bytes(rand::random());
|
||||
if let Some(entity) = self.create_with_uuid(uuid) {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
pub fn create(&mut self) -> (EntityId, EntityMut) {
|
||||
self.create_with_uuid(Uuid::from_bytes(rand::random()))
|
||||
.expect("UUID collision")
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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) {
|
||||
Entry::Occupied(_) => None,
|
||||
Entry::Vacant(ve) => {
|
||||
let entity = EntityId(self.0.sm.insert(Entity {
|
||||
let (id, entity) = self.0.sm.insert(Entity {
|
||||
flags: EntityFlags(0),
|
||||
meta: EntityMeta::new(EntityType::Marker),
|
||||
new_position: Vec3::default(),
|
||||
|
@ -107,16 +103,15 @@ impl<'a> EntitiesMut<'a> {
|
|||
yaw: 0.0,
|
||||
pitch: 0.0,
|
||||
head_yaw: 0.0,
|
||||
head_pitch: 0.0,
|
||||
velocity: Vec3::default(),
|
||||
uuid,
|
||||
}));
|
||||
});
|
||||
|
||||
ve.insert(entity);
|
||||
ve.insert(EntityId(id));
|
||||
|
||||
// TODO: insert into partition.
|
||||
|
||||
Some(entity)
|
||||
Some((EntityId(id), EntityMut(entity)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -188,7 +183,6 @@ pub struct Entity {
|
|||
yaw: f32,
|
||||
pitch: f32,
|
||||
head_yaw: f32,
|
||||
head_pitch: f32,
|
||||
velocity: Vec3<f32>,
|
||||
uuid: Uuid,
|
||||
}
|
||||
|
|
|
@ -76,11 +76,11 @@ impl<T> SlotMap<T> {
|
|||
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)
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
if self.next_free_head == self.slots.len() as u32 {
|
||||
|
@ -93,7 +93,12 @@ impl<T> SlotMap<T> {
|
|||
value: f(key),
|
||||
version: key.version(),
|
||||
});
|
||||
key
|
||||
|
||||
let value = match self.slots.last_mut() {
|
||||
Some(Slot::Occupied { value, .. }) => value,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
(key, value)
|
||||
} else {
|
||||
let slot = &mut self.slots[self.next_free_head as usize];
|
||||
|
||||
|
@ -109,9 +114,15 @@ impl<T> SlotMap<T> {
|
|||
version: key.version(),
|
||||
};
|
||||
|
||||
let value = match slot {
|
||||
Slot::Occupied { value, .. } => value,
|
||||
Slot::Free { .. } => unreachable!(),
|
||||
};
|
||||
|
||||
self.next_free_head = next_free;
|
||||
self.count += 1;
|
||||
key
|
||||
|
||||
(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,14 +245,14 @@ mod tests {
|
|||
fn insert_remove() {
|
||||
let mut sm = SlotMap::new();
|
||||
|
||||
let k0 = sm.insert(10);
|
||||
let k1 = sm.insert(20);
|
||||
let k2 = sm.insert(30);
|
||||
let k0 = sm.insert(10).0;
|
||||
let k1 = sm.insert(20).0;
|
||||
let k2 = sm.insert(30).0;
|
||||
|
||||
assert_eq!(sm.remove(k1), Some(20));
|
||||
assert_eq!(sm.get(k1), None);
|
||||
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_mut(k3), Some(&mut 40));
|
||||
assert_eq!(sm.remove(k0), Some(10));
|
||||
|
@ -254,9 +265,9 @@ mod tests {
|
|||
fn retain() {
|
||||
let mut sm = SlotMap::new();
|
||||
|
||||
let k0 = sm.insert(10);
|
||||
let k1 = sm.insert(20);
|
||||
let k2 = sm.insert(30);
|
||||
let k0 = sm.insert(10).0;
|
||||
let k1 = sm.insert(20).0;
|
||||
let k2 = sm.insert(30).0;
|
||||
|
||||
sm.retain(|k, _| k == k1);
|
||||
|
||||
|
|
18
src/util.rs
18
src/util.rs
|
@ -53,3 +53,21 @@ where
|
|||
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn create(&mut self, dim: DimensionId) -> WorldId {
|
||||
WorldId(self.0.sm.insert(World {
|
||||
pub fn create(&mut self, dim: DimensionId) -> (WorldId, WorldMut) {
|
||||
let (id, world) = self.0.sm.insert(World {
|
||||
clients: Clients::new(),
|
||||
entities: Entities::new(),
|
||||
chunks: Chunks::new(
|
||||
|
@ -67,7 +67,9 @@ impl<'a> WorldsMut<'a> {
|
|||
dimension: dim,
|
||||
is_flat: false,
|
||||
},
|
||||
}))
|
||||
});
|
||||
|
||||
(WorldId(id), WorldMut::new(world))
|
||||
}
|
||||
|
||||
/// Deletes a world from the server. Any [`WorldId`] referring to the
|
||||
|
|
Loading…
Reference in a new issue