Resumed to project. INIT
This commit is contained in:
Executable
+72
@@ -0,0 +1,72 @@
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
pub struct Image {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub pixels: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
/// Load an 8-bit indexed TGA (type 1 or type 9) from disk.
|
||||
pub fn from_tga(path: &str) -> Self {
|
||||
let mut file = File::open(path).unwrap();
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data).unwrap();
|
||||
|
||||
let image_type = data[2];
|
||||
let width = u16::from_le_bytes([data[12], data[13]]) as u32;
|
||||
let height = u16::from_le_bytes([data[14], data[15]]) as u32;
|
||||
let colormap_entries = u16::from_le_bytes([data[5], data[6]]) as usize;
|
||||
let colormap_entry_bytes = data[7] as usize / 8;
|
||||
let pixel_offset = 18 + colormap_entries * colormap_entry_bytes;
|
||||
|
||||
let pixel_count = (width * height) as usize;
|
||||
let mut pixels = vec![0u8; pixel_count];
|
||||
match image_type {
|
||||
1 => pixels.copy_from_slice(&data[pixel_offset..pixel_offset + pixel_count]),
|
||||
9 => decode_rle(&data[pixel_offset..], &mut pixels),
|
||||
_ => panic!("unsupported TGA image type: {}", image_type),
|
||||
}
|
||||
|
||||
Image { width, height, pixels }
|
||||
}
|
||||
|
||||
/// Split this image into 8×8 tiles, row-major. Tile ID = flat index into the Vec.
|
||||
pub fn to_tileset(&self) -> Vec<[u8; 64]> {
|
||||
let w = self.width as usize;
|
||||
let cols = w / 8;
|
||||
let rows = self.height as usize / 8;
|
||||
let mut tiles = Vec::with_capacity(rows * cols);
|
||||
for row in 0..rows {
|
||||
for col in 0..cols {
|
||||
let mut tile = [0u8; 64];
|
||||
for tr in 0..8 {
|
||||
for tc in 0..8 {
|
||||
tile[tr * 8 + tc] = self.pixels[w * (row * 8 + tr) + col * 8 + tc];
|
||||
}
|
||||
}
|
||||
tiles.push(tile);
|
||||
}
|
||||
}
|
||||
tiles
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_rle(src: &[u8], dst: &mut [u8]) {
|
||||
let mut si = 0;
|
||||
let mut di = 0;
|
||||
while di < dst.len() {
|
||||
let header = src[si];
|
||||
si += 1;
|
||||
let count = (header & 0x7f) as usize + 1;
|
||||
if header & 0x80 != 0 {
|
||||
dst[di..di + count].fill(src[si]);
|
||||
si += 1;
|
||||
} else {
|
||||
dst[di..di + count].copy_from_slice(&src[si..si + count]);
|
||||
si += count;
|
||||
}
|
||||
di += count;
|
||||
}
|
||||
}
|
||||
Executable
+124
@@ -0,0 +1,124 @@
|
||||
mod pixelhelper;
|
||||
|
||||
use crate::assets::Image;
|
||||
use crate::input::{GameAction, InputState};
|
||||
use crate::net::{EntityInfo, NetClient, NetEvent};
|
||||
use shared::{chunk_id, player_action};
|
||||
|
||||
pub enum GameSignal {
|
||||
Quit,
|
||||
}
|
||||
|
||||
pub struct Game {
|
||||
#[allow(dead_code)]
|
||||
tileset: Vec<[u8; 64]>,
|
||||
entity_tileset: Vec<[u8; 64]>,
|
||||
net: NetClient,
|
||||
player_entity_id: u32,
|
||||
player_pos: (i32, i32),
|
||||
entities: Vec<EntityInfo>,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
pub fn start() -> Self {
|
||||
let image = Image::from_tga("assets/tilesets/overworld.tga");
|
||||
let tileset = image.to_tileset();
|
||||
let entity_tileset = Image::from_tga("assets/tilesets/entities.tga").to_tileset();
|
||||
|
||||
let server_addr = "127.0.0.1:7777".parse().unwrap();
|
||||
Game {
|
||||
tileset,
|
||||
entity_tileset,
|
||||
net: NetClient::new(server_addr),
|
||||
player_entity_id: 0,
|
||||
player_pos: (16, 16),
|
||||
entities: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, render_frame: &mut [u8], _dt: usize, input: &InputState)
|
||||
-> Option<GameSignal>
|
||||
{
|
||||
if input.button_held(GameAction::Up) { self.net.send_action(player_action::NORTH); }
|
||||
if input.button_held(GameAction::Down) { self.net.send_action(player_action::SOUTH); }
|
||||
if input.button_held(GameAction::Left) { self.net.send_action(player_action::WEST); }
|
||||
if input.button_held(GameAction::Right) { self.net.send_action(player_action::EAST); }
|
||||
|
||||
if input.button_pressed(GameAction::Confirm) {
|
||||
self.net.send_ping();
|
||||
println!("ping sent");
|
||||
}
|
||||
|
||||
for event in self.net.poll() {
|
||||
match event {
|
||||
NetEvent::Pong { rtt_ms } => println!("pong! rtt = {rtt_ms} ms"),
|
||||
NetEvent::State { tick, player_entity_id } => {
|
||||
println!("state tick={tick} player_entity_id={player_entity_id}");
|
||||
self.player_entity_id = player_entity_id;
|
||||
}
|
||||
NetEvent::Chunk { chunk_id } => println!("chunk cached id={chunk_id}"),
|
||||
NetEvent::Entity { entities } => {
|
||||
self.player_pos = entities.iter()
|
||||
.find(|e| e.id == self.player_entity_id)
|
||||
.map(|e| (e.pos_x as i32, e.pos_y as i32))
|
||||
.unwrap_or(self.player_pos);
|
||||
self.entities = entities;
|
||||
}
|
||||
NetEvent::Disconnected => println!("disconnected from server"),
|
||||
}
|
||||
}
|
||||
|
||||
if input.button_pressed(GameAction::Cancel) {
|
||||
println!("Goodbye!");
|
||||
return Some(GameSignal::Quit);
|
||||
}
|
||||
|
||||
self.render_viewport(render_frame);
|
||||
None
|
||||
}
|
||||
|
||||
fn render_viewport(&self, frame: &mut [u8]) {
|
||||
const WALL: u8 = 0x00;
|
||||
const W: usize = 320;
|
||||
|
||||
frame.fill(0);
|
||||
|
||||
let ox = self.player_pos.0 - 15;
|
||||
let oy = self.player_pos.1 - 15;
|
||||
|
||||
for vy in 0..30i32 {
|
||||
for vx in 0..30i32 {
|
||||
let wx = ox + vx;
|
||||
let wy = oy + vy;
|
||||
let cid = chunk_id(wx.div_euclid(32) as i16, wy.div_euclid(32) as i16);
|
||||
let tile_idx = wy.rem_euclid(32) as usize * 32 + wx.rem_euclid(32) as usize;
|
||||
let tile_id = self.net.chunk_cache.get(&cid)
|
||||
.map(|c| c.tiles[tile_idx])
|
||||
.unwrap_or(0);
|
||||
let px = vx * 8;
|
||||
let py = vy * 8;
|
||||
if let Some(tile) = self.tileset.get(tile_id as usize) {
|
||||
pixelhelper::blit_tile(frame, W, px, py, tile);
|
||||
} else {
|
||||
for dy in 0..8 {
|
||||
for dx in 0..8 {
|
||||
pixelhelper::set_pixel(frame, W, px + dx, py + dy, WALL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Entity pass
|
||||
let ox = self.player_pos.0 - 15;
|
||||
let oy = self.player_pos.1 - 15;
|
||||
for e in &self.entities {
|
||||
let vx = e.pos_x as i32 - ox;
|
||||
let vy = e.pos_y as i32 - oy;
|
||||
if vx < 0 || vx >= 30 || vy < 0 || vy >= 30 { continue; }
|
||||
if let Some(tile) = self.entity_tileset.get(e.type_id as usize) {
|
||||
pixelhelper::blit_tile(frame, W, vx * 8, vy * 8, tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
/// Write a single palette index. Out-of-bounds coordinates are silently ignored.
|
||||
#[allow(dead_code)]
|
||||
pub fn set_pixel(dst: &mut [u8], dst_w: usize, x: i32, y: i32, color: u8) {
|
||||
let dst_h = (dst.len() / dst_w) as i32;
|
||||
if x < 0 || x >= dst_w as i32 || y < 0 || y >= dst_h {
|
||||
return;
|
||||
}
|
||||
dst[dst_w * y as usize + x as usize] = color;
|
||||
}
|
||||
|
||||
/// Copy a rectangle out of `src` into a new Vec (for building sprites/glyphs).
|
||||
#[allow(dead_code)]
|
||||
pub fn extract(src: &[u8], src_w: usize, x: usize, y: usize, w: usize, h: usize) -> Vec<u8> {
|
||||
let mut out = vec![0u8; w * h];
|
||||
for row in 0..h {
|
||||
let s = src_w * (y + row) + x;
|
||||
let d = w * row;
|
||||
out[d..d + w].copy_from_slice(&src[s..s + w]);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Blit `src` (width `src_w`, height derived from src.len()) into `dst` at (dx, dy).
|
||||
/// Clips against all four edges of `dst`; the destination position may be negative.
|
||||
#[allow(dead_code)]
|
||||
pub fn blit(dst: &mut [u8], dst_w: usize, dx: i32, dy: i32, src: &[u8], src_w: usize) {
|
||||
let dst_w_i = dst_w as i32;
|
||||
let dst_h = (dst.len() / dst_w) as i32;
|
||||
let src_h = (src.len() / src_w) as i32;
|
||||
|
||||
for row in 0..src_h {
|
||||
let y = dy + row;
|
||||
if y < 0 || y >= dst_h {
|
||||
continue;
|
||||
}
|
||||
// Clip the horizontal span once per row, then copy it in one shot.
|
||||
let x0 = dx.max(0);
|
||||
let x1 = (dx + src_w as i32).min(dst_w_i);
|
||||
if x0 >= x1 {
|
||||
continue;
|
||||
}
|
||||
let d = dst_w * y as usize + x0 as usize;
|
||||
let s = (row * src_w as i32 + (x0 - dx)) as usize;
|
||||
let len = (x1 - x0) as usize;
|
||||
dst[d..d + len].copy_from_slice(&src[s..s + len]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Blit an 8×8 tile with transparency: index 0 is skipped.
|
||||
/// Clips against all four edges of `dst`; the destination position may be negative.
|
||||
pub fn blit_tile(dst: &mut [u8], dst_w: usize, dx: i32, dy: i32, tile: &[u8; 64]) {
|
||||
let dst_w_i = dst_w as i32;
|
||||
let dst_h = (dst.len() / dst_w) as i32;
|
||||
|
||||
for row in 0..8i32 {
|
||||
let y = dy + row;
|
||||
if y < 0 || y >= dst_h {
|
||||
continue;
|
||||
}
|
||||
for col in 0..8i32 {
|
||||
let x = dx + col;
|
||||
if x < 0 || x >= dst_w_i {
|
||||
continue;
|
||||
}
|
||||
let px = tile[(row * 8 + col) as usize];
|
||||
if px != 0 {
|
||||
dst[dst_w * y as usize + x as usize] = px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
use pbio::Key;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum GameAction {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
Confirm,
|
||||
Cancel,
|
||||
}
|
||||
|
||||
pub struct InputMap {
|
||||
bindings: Vec<(Key, GameAction)>,
|
||||
}
|
||||
|
||||
impl InputMap {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
bindings: vec![
|
||||
(Key::Up, GameAction::Up),
|
||||
(Key::Down, GameAction::Down),
|
||||
(Key::Left, GameAction::Left),
|
||||
(Key::Right, GameAction::Right),
|
||||
(Key::W, GameAction::Up),
|
||||
(Key::S, GameAction::Down),
|
||||
(Key::A, GameAction::Left),
|
||||
(Key::D, GameAction::Right),
|
||||
(Key::Enter, GameAction::Confirm),
|
||||
(Key::Escape, GameAction::Cancel),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn translate(&self, key: Key) -> Option<GameAction> {
|
||||
self.bindings.iter().find(|(k, _)| *k == key).map(|(_, a)| *a)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InputState {
|
||||
pressed: Vec<GameAction>,
|
||||
held: Vec<GameAction>,
|
||||
released: Vec<GameAction>,
|
||||
}
|
||||
|
||||
impl InputState {
|
||||
pub fn new() -> Self {
|
||||
Self { pressed: Vec::new(), held: Vec::new(), released: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn push(&mut self, action: GameAction) {
|
||||
self.pressed.push(action);
|
||||
if !self.held.contains(&action) {
|
||||
self.held.push(action);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn release(&mut self, action: GameAction) {
|
||||
self.held.retain(|a| *a != action);
|
||||
if !self.released.contains(&action) {
|
||||
self.released.push(action);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn button_pressed(&self, action: GameAction) -> bool {
|
||||
self.pressed.contains(&action)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn button_held(&self, action: GameAction) -> bool {
|
||||
self.held.contains(&action)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn button_released(&self, action: GameAction) -> bool {
|
||||
self.released.contains(&action)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.pressed.clear();
|
||||
self.released.clear();
|
||||
}
|
||||
}
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
use pbio::{Event, Platform, PlatformConfig};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
mod assets;
|
||||
mod game;
|
||||
mod input;
|
||||
mod net;
|
||||
|
||||
fn main() {
|
||||
let mut plat = Platform::new(PlatformConfig {
|
||||
title: "Roguelike".into(),
|
||||
window_size: (800, 600),
|
||||
framebuffer_size: (320, 240),
|
||||
aspect_ratio: Some(4.0 / 3.0),
|
||||
palette: pbio::palette::RGB332,
|
||||
vsync: true,
|
||||
mouse_capture: false,
|
||||
mouse_visible: true,
|
||||
});
|
||||
|
||||
let input_map = input::InputMap::new();
|
||||
let mut input_state = input::InputState::new();
|
||||
let mut last_update = Instant::now();
|
||||
let mut game = game::Game::start();
|
||||
|
||||
while !plat.should_close() {
|
||||
plat.poll_events(Some(Duration::from_millis(20)));
|
||||
|
||||
let events: Vec<Event> = plat.drain_events().collect();
|
||||
for ev in events {
|
||||
match ev {
|
||||
Event::Key { key, pressed: true, .. } => {
|
||||
if let Some(action) = input_map.translate(key) {
|
||||
input_state.push(action);
|
||||
}
|
||||
}
|
||||
Event::Key { key, pressed: false, .. } => {
|
||||
if let Some(action) = input_map.translate(key) {
|
||||
input_state.release(action);
|
||||
}
|
||||
}
|
||||
Event::CloseRequested => plat.request_close(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let dt = last_update.elapsed().as_millis();
|
||||
if dt < 16 { continue; }
|
||||
last_update = Instant::now();
|
||||
|
||||
if let Some(game::GameSignal::Quit) =
|
||||
game.update(plat.framebuffer_mut(), dt as usize, &input_state)
|
||||
{
|
||||
plat.request_close();
|
||||
}
|
||||
input_state.clear();
|
||||
|
||||
plat.present();
|
||||
}
|
||||
}
|
||||
Executable
+208
@@ -0,0 +1,208 @@
|
||||
use std::collections::HashMap;
|
||||
use std::mem::size_of;
|
||||
use std::net::{SocketAddr, UdpSocket};
|
||||
use std::time::{Duration, Instant};
|
||||
use bytemuck::bytes_of;
|
||||
use shared::{ActionPacket, ChunkEntry, ChunkPacket, EntityPacket,
|
||||
Header, PingPacket, PongPacket, StatePacket,
|
||||
MAGIC, VERSION, packet_type};
|
||||
|
||||
pub struct CachedChunk {
|
||||
pub version: u16,
|
||||
pub tiles: Box<[u16; 1024]>, // decoded global tile IDs
|
||||
}
|
||||
|
||||
pub struct EntityInfo {
|
||||
pub id: u32,
|
||||
pub type_id: u16,
|
||||
pub pos_x: i16,
|
||||
pub pos_y: i16,
|
||||
pub hp: u16,
|
||||
pub hp_max: u16,
|
||||
pub flags: u8,
|
||||
}
|
||||
|
||||
pub enum NetEvent {
|
||||
Pong { rtt_ms: u64 },
|
||||
State { tick: u32, player_entity_id: u32 },
|
||||
Chunk { chunk_id: u32 },
|
||||
Entity { entities: Vec<EntityInfo> },
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
const FNV_BASIS: u32 = 2_166_136_261;
|
||||
const FNV_PRIME: u32 = 16_777_619;
|
||||
|
||||
pub struct NetClient {
|
||||
socket: UdpSocket,
|
||||
ping_sent_at: Option<Instant>,
|
||||
action_sequence: u32,
|
||||
last_action_sent: Instant,
|
||||
last_state_received: Option<Instant>,
|
||||
pub chunk_cache: HashMap<u32, CachedChunk>,
|
||||
last_state_chunks: Option<[ChunkEntry; 9]>,
|
||||
pending_entities: Vec<EntityInfo>,
|
||||
last_entity_checksum: u32,
|
||||
last_state_tick: u32,
|
||||
entity_checksum_accum: u32,
|
||||
}
|
||||
|
||||
impl NetClient {
|
||||
pub fn new(server_addr: SocketAddr) -> Self {
|
||||
let socket = UdpSocket::bind("0.0.0.0:0").expect("bind failed");
|
||||
socket.connect(server_addr).expect("connect failed");
|
||||
socket.set_nonblocking(true).expect("set_nonblocking failed");
|
||||
Self {
|
||||
socket,
|
||||
ping_sent_at: None,
|
||||
action_sequence: 0,
|
||||
last_action_sent: Instant::now(),
|
||||
last_state_received: None,
|
||||
chunk_cache: HashMap::new(),
|
||||
last_state_chunks: None,
|
||||
pending_entities: Vec::new(),
|
||||
last_entity_checksum: 0,
|
||||
last_state_tick: 0,
|
||||
entity_checksum_accum: FNV_BASIS,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_action(&mut self, player_action: u16) {
|
||||
self.action_sequence += 1;
|
||||
let packet = ActionPacket {
|
||||
header: Header::new(packet_type::ACTION),
|
||||
auth_token: 0,
|
||||
sequence: self.action_sequence,
|
||||
cache: {
|
||||
let mut cache = [ChunkEntry { chunk_id: 0, version: 0 }; 9];
|
||||
if let Some(slots) = self.last_state_chunks {
|
||||
for (i, slot) in slots.iter().enumerate() {
|
||||
let cid = slot.chunk_id;
|
||||
let ver = self.chunk_cache.get(&cid)
|
||||
.map(|c| c.version).unwrap_or(0);
|
||||
cache[i] = ChunkEntry { chunk_id: cid, version: ver };
|
||||
}
|
||||
}
|
||||
cache
|
||||
},
|
||||
player_action,
|
||||
};
|
||||
self.socket.send(bytes_of(&packet)).ok();
|
||||
self.last_action_sent = Instant::now();
|
||||
}
|
||||
|
||||
pub fn send_ping(&mut self) {
|
||||
let ping = PingPacket { header: Header::new(packet_type::PING), timestamp_ms: 0 };
|
||||
self.ping_sent_at = Some(Instant::now());
|
||||
self.socket.send(bytes_of(&ping)).ok();
|
||||
}
|
||||
|
||||
pub fn poll(&mut self) -> Vec<NetEvent> {
|
||||
const KEEPALIVE: Duration = Duration::from_secs(2);
|
||||
const TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
if self.last_action_sent.elapsed() >= KEEPALIVE {
|
||||
self.send_action(shared::player_action::NOOP);
|
||||
}
|
||||
|
||||
let mut events = Vec::new();
|
||||
let mut buf = [0u8; 1200];
|
||||
|
||||
loop {
|
||||
match self.socket.recv(&mut buf) {
|
||||
Ok(n) => {
|
||||
if n < size_of::<Header>() { continue; }
|
||||
let header: Header = *bytemuck::from_bytes(&buf[..size_of::<Header>()]);
|
||||
if header.magic != MAGIC || header.version != VERSION { continue; }
|
||||
|
||||
match header.packet_type {
|
||||
packet_type::PONG if n >= size_of::<PongPacket>() => {
|
||||
if let Some(sent_at) = self.ping_sent_at.take() {
|
||||
let rtt_ms = sent_at.elapsed().as_millis() as u64;
|
||||
events.push(NetEvent::Pong { rtt_ms });
|
||||
}
|
||||
}
|
||||
packet_type::STATE if n >= size_of::<StatePacket>() => {
|
||||
let pkt: StatePacket = *bytemuck::from_bytes(&buf[..size_of::<StatePacket>()]);
|
||||
self.last_state_received = Some(Instant::now());
|
||||
self.last_state_chunks = Some(pkt.chunks);
|
||||
self.last_entity_checksum = pkt.entity_checksum;
|
||||
self.last_state_tick = pkt.tick;
|
||||
self.pending_entities.clear();
|
||||
self.entity_checksum_accum = FNV_BASIS;
|
||||
events.push(NetEvent::State { tick: pkt.tick, player_entity_id: pkt.player_entity_id });
|
||||
}
|
||||
packet_type::ENTITY if n >= size_of::<EntityPacket>() => {
|
||||
let pkt: EntityPacket = *bytemuck::from_bytes(&buf[..size_of::<EntityPacket>()]);
|
||||
if pkt.tick != self.last_state_tick {
|
||||
// stale datagram from a previous state cycle
|
||||
} else {
|
||||
for i in 0..pkt.entity_count as usize {
|
||||
let e = pkt.entities[i];
|
||||
for byte in bytemuck::bytes_of(&e) {
|
||||
self.entity_checksum_accum ^= *byte as u32;
|
||||
self.entity_checksum_accum =
|
||||
self.entity_checksum_accum.wrapping_mul(FNV_PRIME);
|
||||
}
|
||||
self.pending_entities.push(EntityInfo {
|
||||
id: e.id,
|
||||
type_id: e.type_id,
|
||||
pos_x: e.pos_x,
|
||||
pos_y: e.pos_y,
|
||||
hp: e.hp,
|
||||
hp_max: e.hp_max,
|
||||
flags: e.entity_flags,
|
||||
});
|
||||
}
|
||||
if pkt.packet_flags & 1 == 0 {
|
||||
// last datagram in sequence
|
||||
if self.entity_checksum_accum != self.last_entity_checksum {
|
||||
self.send_action(shared::player_action::NOOP);
|
||||
}
|
||||
events.push(NetEvent::Entity {
|
||||
entities: std::mem::take(&mut self.pending_entities),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
packet_type::CHUNK if n >= size_of::<ChunkPacket>() => {
|
||||
let pkt: ChunkPacket = *bytemuck::from_bytes(&buf[..size_of::<ChunkPacket>()]);
|
||||
let id = pkt.chunk.chunk_id;
|
||||
let version = pkt.chunk.version;
|
||||
let palette = pkt.palette;
|
||||
let raw = pkt.tiles;
|
||||
let mut tiles = Box::new([0u16; 1024]);
|
||||
for i in 0..256usize {
|
||||
let b0 = raw[i * 3];
|
||||
let b1 = raw[i * 3 + 1];
|
||||
let b2 = raw[i * 3 + 2];
|
||||
let ia = (b0 & 0x3F) as usize;
|
||||
let ib = ((b0 >> 6) | ((b1 & 0x0F) << 2)) as usize;
|
||||
let ic = ((b1 >> 4) | ((b2 & 0x03) << 4)) as usize;
|
||||
let id_ = (b2 >> 2) as usize;
|
||||
tiles[i * 4] = palette[ia];
|
||||
tiles[i * 4 + 1] = palette[ib];
|
||||
tiles[i * 4 + 2] = palette[ic];
|
||||
tiles[i * 4 + 3] = palette[id_];
|
||||
}
|
||||
self.chunk_cache.insert(id, CachedChunk { version, tiles });
|
||||
events.push(NetEvent::Chunk { chunk_id: id });
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break,
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(t) = self.last_state_received {
|
||||
if t.elapsed() >= TIMEOUT {
|
||||
self.last_state_received = None;
|
||||
events.push(NetEvent::Disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
events
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user