use std::time::{Duration, Instant};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::io;
use glam::Vec2;
use tracing::{warn, info};
use mc173::world::{Dimension, Weather};
use mc173::entity::{self as e};
use crate::config;
use crate::proto::{self, Network, NetworkEvent, NetworkClient, InPacket, OutPacket};
use crate::offline::OfflinePlayer;
use crate::player::ServerPlayer;
use crate::world::ServerWorld;
const TICK_DURATION: Duration = Duration::from_millis(50);
pub struct Server {
net: Network,
clients: HashMap<NetworkClient, ClientState>,
worlds: Vec<ServerWorld>,
offline_players: HashMap<String, OfflinePlayer>,
}
impl Server {
pub fn bind(addr: SocketAddr) -> io::Result<Self> {
info!("server bound to {addr}");
Ok(Self {
net: Network::bind(addr)?,
clients: HashMap::new(),
worlds: vec![
ServerWorld::new("overworld"),
],
offline_players: HashMap::new(),
})
}
pub fn save(&mut self) {
for world in &mut self.worlds {
world.save();
}
}
pub fn tick_padded(&mut self) -> io::Result<()> {
let start = Instant::now();
self.tick()?;
let elapsed = start.elapsed();
if let Some(missing) = TICK_DURATION.checked_sub(elapsed) {
std::thread::sleep(missing);
} else {
warn!("tick take too long {:?}, expected {:?}", elapsed, TICK_DURATION);
}
Ok(())
}
pub fn tick(&mut self) -> io::Result<()> {
self.tick_net()?;
for world in &mut self.worlds {
world.tick();
}
Ok(())
}
fn tick_net(&mut self) -> io::Result<()> {
while let Some(event) = self.net.poll()? {
match event {
NetworkEvent::Accept { client } =>
self.handle_accept(client),
NetworkEvent::Lost { client, error } =>
self.handle_lost(client, error),
NetworkEvent::Packet { client, packet } =>
self.handle_packet(client, packet),
}
}
Ok(())
}
fn handle_accept(&mut self, client: NetworkClient) {
info!("accept client #{}", client.id());
self.clients.insert(client, ClientState::Handshaking);
}
fn handle_lost(&mut self, client: NetworkClient, error: Option<io::Error>) {
info!("lost client #{}: {:?}", client.id(), error);
let state = self.clients.remove(&client).unwrap();
if let ClientState::Playing { world_index, player_index } = state {
let world = &mut self.worlds[world_index];
if let Some(swapped_player) = world.handle_player_leave(player_index, true) {
let state = self.clients.get_mut(&swapped_player.client)
.expect("swapped player should be existing");
*state = ClientState::Playing { world_index, player_index };
}
}
}
fn handle_packet(&mut self, client: NetworkClient, packet: InPacket) {
match *self.clients.get(&client).unwrap() {
ClientState::Handshaking => {
self.handle_handshaking(client, packet);
}
ClientState::Playing { world_index, player_index } => {
let world = &mut self.worlds[world_index];
let player = &mut world.players[player_index];
player.handle(&mut world.world, &mut world.state, packet);
}
}
}
fn handle_handshaking(&mut self, client: NetworkClient, packet: InPacket) {
match packet {
InPacket::KeepAlive => {}
InPacket::Handshake(_) =>
self.handle_handshake(client),
InPacket::Login(packet) =>
self.handle_login(client, packet),
_ => self.send_disconnect(client, format!("Invalid packet: {packet:?}"))
}
}
fn handle_handshake(&mut self, client: NetworkClient) {
self.net.send(client, OutPacket::Handshake(proto::OutHandshakePacket {
server: "-".to_string(),
}));
}
fn handle_login(&mut self, client: NetworkClient, packet: proto::InLoginPacket) {
if packet.protocol_version != 14 {
self.send_disconnect(client, format!("Protocol version mismatch!"));
return;
}
let spawn_pos = config::SPAWN_POS;
let offline_player = self.offline_players.entry(packet.username.clone())
.or_insert_with(|| {
let spawn_world = &self.worlds[0];
OfflinePlayer {
world: spawn_world.state.name.clone(),
pos: spawn_pos,
look: Vec2::ZERO,
}
});
let (world_index, world) = self.worlds.iter_mut()
.enumerate()
.filter(|(_, world)| world.state.name == offline_player.world)
.next()
.expect("invalid offline player world name");
let entity = e::Human::new_with(|base, living, player| {
base.pos = offline_player.pos;
base.look = offline_player.look;
base.persistent = false;
base.can_pickup = true;
living.artificial = true;
living.health = 200; player.username = packet.username.clone();
});
let entity_id = world.world.spawn_entity(entity);
world.world.set_entity_player(entity_id, true);
self.net.send(client, OutPacket::Login(proto::OutLoginPacket {
entity_id,
random_seed: world.state.seed,
dimension: match world.world.get_dimension() {
Dimension::Overworld => 0,
Dimension::Nether => -1,
},
}));
self.net.send(client, OutPacket::SpawnPosition(proto::SpawnPositionPacket {
pos: spawn_pos.as_ivec3(),
}));
self.net.send(client, OutPacket::PositionLook(proto::PositionLookPacket {
pos: offline_player.pos,
stance: offline_player.pos.y + 1.62,
look: offline_player.look,
on_ground: false,
}));
self.net.send(client, OutPacket::UpdateTime(proto::UpdateTimePacket {
time: world.world.get_time(),
}));
if world.world.get_weather() != Weather::Clear {
self.net.send(client, OutPacket::Notification(proto::NotificationPacket {
reason: 1,
}));
}
let server_player = ServerPlayer::new(&self.net, client, entity_id, packet.username, &offline_player);
let player_index = world.handle_player_join(server_player);
let previous_state = self.clients.insert(client, ClientState::Playing {
world_index,
player_index,
});
debug_assert_eq!(previous_state, Some(ClientState::Handshaking));
}
fn send_disconnect(&mut self, client: NetworkClient, reason: String) {
self.net.send(client, OutPacket::Disconnect(proto::DisconnectPacket {
reason,
}))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ClientState {
Handshaking,
Playing {
world_index: usize,
player_index: usize,
}
}