mirror of
https://github.com/griffi-gh/kubi.git
synced 2024-11-25 16:28:42 -06:00
Compare commits
12 commits
043bb873c4
...
772a8ea7db
Author | SHA1 | Date | |
---|---|---|---|
griffi-gh | 772a8ea7db | ||
griffi-gh | 6f84d9014a | ||
griffi-gh | 6ee282e744 | ||
griffi-gh | 5d8906cfb1 | ||
griffi-gh | 8c4ef7f83f | ||
griffi-gh | b8c7dcc196 | ||
griffi-gh | dc1a641887 | ||
griffi-gh | e373aa758c | ||
griffi-gh | 907a5845fa | ||
griffi-gh | 740da98cbd | ||
griffi-gh | af3c938a03 | ||
griffi-gh | 50cc36e3d5 |
489
Cargo.lock
generated
489
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,14 @@
|
|||
use shipyard::{Unique, UniqueView, UniqueViewMut, Workload, IntoWorkload, AllStoragesView, View, Get, NonSendSync, IntoIter};
|
||||
use glam::IVec3;
|
||||
use hashbrown::HashMap;
|
||||
use kubi_shared::networking::{
|
||||
channels::Channel, client::{Client, ClientId}, messages::{ClientToServerMessage, ClientToServerMessageType, ServerToClientMessage}
|
||||
use kubi_shared::{
|
||||
chunk::CHUNK_SIZE,
|
||||
queue::QueuedBlock,
|
||||
networking::{
|
||||
channels::Channel,
|
||||
client::{Client, ClientId},
|
||||
messages::{ClientToServerMessage, ClientToServerMessageType, ServerToClientMessage}
|
||||
},
|
||||
};
|
||||
use uflow::{server::RemoteClient, SendMode};
|
||||
use lz4_flex::compress_prepend_size as lz4_compress;
|
||||
|
@ -21,7 +27,15 @@ pub mod tasks;
|
|||
|
||||
use chunk::Chunk;
|
||||
|
||||
use self::{tasks::{ChunkTaskManager, ChunkTask, ChunkTaskResponse, init_chunk_task_manager}, chunk::ChunkState};
|
||||
use self::{
|
||||
tasks::{ChunkTaskManager, ChunkTask, ChunkTaskResponse, init_chunk_task_manager},
|
||||
chunk::ChunkState
|
||||
};
|
||||
|
||||
#[derive(Unique, Default)]
|
||||
pub struct LocalBlockQueue {
|
||||
pub queue: Vec<QueuedBlock>,
|
||||
}
|
||||
|
||||
#[derive(Unique, Default)]
|
||||
pub struct ChunkManager {
|
||||
|
@ -106,6 +120,7 @@ fn process_finished_tasks(
|
|||
mut chunk_manager: UniqueViewMut<ChunkManager>,
|
||||
id_map: UniqueView<ClientIdMap>,
|
||||
client_addr: View<ClientAddress>,
|
||||
mut local_queue: UniqueViewMut<LocalBlockQueue>,
|
||||
) {
|
||||
'outer: while let Some(res) = task_manager.receive() {
|
||||
let ChunkTaskResponse::ChunkLoaded { chunk_position, blocks, queue } = res;
|
||||
|
@ -120,12 +135,14 @@ fn process_finished_tasks(
|
|||
chunk.state = ChunkState::Loaded;
|
||||
chunk.blocks = Some(blocks.clone());
|
||||
|
||||
local_queue.queue.extend_from_slice(&queue);
|
||||
|
||||
log::debug!("Chunk {chunk_position} loaded, {} subs", chunk.subscriptions.len());
|
||||
|
||||
let chunk_packet = &ServerToClientMessage::ChunkResponse {
|
||||
chunk: chunk_position,
|
||||
data: blocks,
|
||||
queued: queue
|
||||
queued: queue //should this be here?
|
||||
};
|
||||
|
||||
for &subscriber in &chunk.subscriptions {
|
||||
|
@ -157,6 +174,7 @@ fn process_block_queue_messages(
|
|||
addr_map: UniqueView<ClientAddressMap>,
|
||||
clients: View<Client>,
|
||||
addrs: View<ClientAddress>,
|
||||
mut queue: UniqueViewMut<LocalBlockQueue>,
|
||||
) {
|
||||
for event in &events.0 {
|
||||
let Some(message) = check_message_auth
|
||||
|
@ -164,7 +182,10 @@ fn process_block_queue_messages(
|
|||
(&server, event, &clients, &addr_map) else { continue };
|
||||
|
||||
let ClientToServerMessage::QueueBlock { item } = message.message else { unreachable!() };
|
||||
//TODO place in our own queue, for now just send to other clients
|
||||
|
||||
//place in our local world
|
||||
queue.queue.push(item);
|
||||
|
||||
log::info!("Placed block {:?} at {}", item.block_type, item.position);
|
||||
for (other_client, other_client_address) in (&clients, &addrs).iter() {
|
||||
//No need to send the event back
|
||||
|
@ -188,23 +209,48 @@ fn process_block_queue_messages(
|
|||
}
|
||||
}
|
||||
|
||||
fn init_chunk_manager(
|
||||
fn process_block_queue(
|
||||
mut chunk_manager: UniqueViewMut<ChunkManager>,
|
||||
mut queue: UniqueViewMut<LocalBlockQueue>,
|
||||
) {
|
||||
let initial_len = queue.queue.len();
|
||||
queue.queue.retain(|item| {
|
||||
let chunk_position = item.position.div_euclid(IVec3::splat(CHUNK_SIZE as i32));
|
||||
let block_position = item.position.rem_euclid(IVec3::splat(CHUNK_SIZE as i32));
|
||||
let Some(chunk) = chunk_manager.chunks.get_mut(&chunk_position) else {
|
||||
return true
|
||||
};
|
||||
let Some(blocks) = &mut chunk.blocks else {
|
||||
return true
|
||||
};
|
||||
blocks[block_position.x as usize][block_position.y as usize][block_position.z as usize] = item.block_type;
|
||||
false
|
||||
});
|
||||
if initial_len != queue.queue.len() {
|
||||
log::debug!("queue processed {}/{} items", initial_len - queue.queue.len(), initial_len);
|
||||
}
|
||||
}
|
||||
|
||||
/// init local block queue and chunk manager
|
||||
fn init_chunk_manager_and_block_queue(
|
||||
storages: AllStoragesView
|
||||
) {
|
||||
storages.add_unique(ChunkManager::new());
|
||||
storages.add_unique(LocalBlockQueue::default());
|
||||
}
|
||||
|
||||
pub fn init_world() -> Workload {
|
||||
(
|
||||
init_chunk_manager,
|
||||
init_chunk_manager_and_block_queue,
|
||||
init_chunk_task_manager,
|
||||
).into_workload()
|
||||
}
|
||||
|
||||
pub fn update_world() -> Workload {
|
||||
(
|
||||
process_chunk_requests,
|
||||
process_finished_tasks,
|
||||
process_block_queue_messages,
|
||||
).into_workload()
|
||||
process_block_queue,
|
||||
process_chunk_requests,
|
||||
).into_sequential_workload()
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ winit = { version = "0.29", features = ["android-native-activity"] }
|
|||
glutin-winit = "0.4"
|
||||
raw-window-handle = "=0.5.*"
|
||||
glam = { version = "0.27", features = ["debug-glam-assert", "fast-math"] }
|
||||
image = { version = "0.24", default_features = false, features = ["png"] }
|
||||
image = { version = "0.25", default_features = false, features = ["png"] }
|
||||
strum = { version = "0.26", features = ["derive"] }
|
||||
hashbrown = "0.14"
|
||||
nohash-hasher = "0.2"
|
||||
|
@ -35,13 +35,14 @@ lz4_flex = { version = "0.11", default-features = false, features = ["std"] }
|
|||
static_assertions = "1.1"
|
||||
tinyset = "0.4"
|
||||
serde_json = { version = "1.0", optional = true } #only used for `generate_visualizer_data`
|
||||
rand = { version = "0.8", features = ["alloc", "small_rng"]}
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android-activity = "^0.5.2"
|
||||
ndk = "0.8"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = "0.3"
|
||||
winapi = { version = "0.3", features = ["wincon"] }
|
||||
|
||||
[features]
|
||||
default = ["raw-evt"]
|
||||
|
@ -49,7 +50,7 @@ raw-evt = [] #required for mouse input, but breaks keyboard on android
|
|||
generate_visualizer_data = ["dep:serde_json", "shipyard/serde1"]
|
||||
safe_lz4 = ["lz4_flex/safe-encode", "lz4_flex/safe-decode"]
|
||||
parallel = ["shipyard/parallel"] # causes some serious issues!
|
||||
nightly = ["hashbrown/nightly", "glam/core-simd", "static_assertions/nightly", "lz4_flex/nightly", "kubi-shared/nightly"]
|
||||
nightly = ["hashbrown/nightly", "glam/core-simd", "static_assertions/nightly", "lz4_flex/nightly", "kubi-shared/nightly", "rand/nightly"]
|
||||
|
||||
#part of wip android support
|
||||
[package.metadata.android]
|
||||
|
|
|
@ -1,48 +1,49 @@
|
|||
use glam::{Vec3, Mat4};
|
||||
use shipyard::{ViewMut, View, IntoIter, Workload, IntoWorkload, track, UniqueView, SystemModificator};
|
||||
use crate::{transform::Transform, rendering::WindowSize, events::WindowResizedEvent};
|
||||
use super::Camera;
|
||||
|
||||
//maybe parallelize these two?
|
||||
|
||||
fn update_view_matrix(
|
||||
mut vm_camera: ViewMut<Camera>,
|
||||
v_transform: View<Transform, track::All>
|
||||
) {
|
||||
for (mut camera, transform) in (&mut vm_camera, v_transform.inserted_or_modified()).iter() {
|
||||
let (_, rotation, translation) = transform.0.to_scale_rotation_translation();
|
||||
let direction = (rotation.normalize() * Vec3::NEG_Z).normalize();
|
||||
camera.view_matrix = Mat4::look_to_rh(translation, direction, camera.up);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_perspective_matrix(
|
||||
mut vm_camera: ViewMut<Camera>,
|
||||
size: UniqueView<WindowSize>,
|
||||
) {
|
||||
for mut camera in (&mut vm_camera).iter() {
|
||||
camera.perspective_matrix = Mat4::perspective_rh_gl(
|
||||
camera.fov,
|
||||
size.0.x as f32 / size.0.y as f32,
|
||||
camera.z_near,
|
||||
camera.z_far,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn need_perspective_calc(
|
||||
v_camera: View<Camera>,
|
||||
resize_event: View<WindowResizedEvent>,
|
||||
) -> bool {
|
||||
(resize_event.len() > 0) ||
|
||||
(v_camera.iter().any(|camera| {
|
||||
camera.perspective_matrix == Mat4::default()
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn update_matrices() -> Workload {
|
||||
(
|
||||
update_view_matrix,
|
||||
update_perspective_matrix.run_if(need_perspective_calc),
|
||||
).into_sequential_workload()
|
||||
}
|
||||
use glam::{Vec3, Mat4};
|
||||
use shipyard::{ViewMut, View, IntoIter, Workload, IntoWorkload, track, UniqueView, SystemModificator};
|
||||
use crate::{transform::Transform, rendering::WindowSize, events::WindowResizedEvent};
|
||||
use super::Camera;
|
||||
|
||||
//maybe parallelize these two?
|
||||
|
||||
fn update_view_matrix(
|
||||
mut vm_camera: ViewMut<Camera>,
|
||||
v_transform: View<Transform, track::All>
|
||||
) {
|
||||
for (mut camera, transform) in (&mut vm_camera, v_transform.inserted_or_modified()).iter() {
|
||||
let (_, rotation, translation) = transform.0.to_scale_rotation_translation();
|
||||
let direction = (rotation.normalize() * Vec3::NEG_Z).normalize();
|
||||
camera.view_matrix = Mat4::look_to_rh(translation, direction, camera.up);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_perspective_matrix(
|
||||
mut vm_camera: ViewMut<Camera>,
|
||||
size: UniqueView<WindowSize>,
|
||||
) {
|
||||
for mut camera in (&mut vm_camera).iter() {
|
||||
camera.perspective_matrix = Mat4::perspective_rh_gl(
|
||||
camera.fov,
|
||||
size.0.x as f32 / size.0.y as f32,
|
||||
camera.z_near,
|
||||
camera.z_far,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn need_perspective_calc(
|
||||
v_camera: View<Camera>,
|
||||
resize_event: View<WindowResizedEvent>,
|
||||
) -> bool {
|
||||
(resize_event.len() > 0) ||
|
||||
(v_camera.iter().any(|camera| {
|
||||
camera.perspective_matrix == Mat4::default()
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn update_matrices() -> Workload {
|
||||
(
|
||||
update_view_matrix,
|
||||
//update_perspective_matrix,
|
||||
update_perspective_matrix.run_if(need_perspective_calc),
|
||||
).into_sequential_workload()
|
||||
}
|
||||
|
|
64
kubi/src/chat.rs
Normal file
64
kubi/src/chat.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use kubi_shared::networking::client::ClientId;
|
||||
use shipyard::{AllStoragesView, Unique, UniqueViewMut};
|
||||
|
||||
pub enum ChatMessage {
|
||||
PlayerMessage {
|
||||
id: ClientId,
|
||||
username: String,
|
||||
message: String,
|
||||
},
|
||||
PlayerJoin {
|
||||
id: ClientId,
|
||||
username: String,
|
||||
},
|
||||
PlayerLeave {
|
||||
id: ClientId,
|
||||
username: String,
|
||||
},
|
||||
System(String),
|
||||
}
|
||||
|
||||
// impl ChatMessage {
|
||||
// pub fn render() -> String {
|
||||
// todo!() //TODO
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Unique, Default)]
|
||||
pub struct ChatHistory {
|
||||
pub messages: Vec<ChatMessage>,
|
||||
}
|
||||
|
||||
impl ChatHistory {
|
||||
pub fn add_message(&mut self, message: ChatMessage) {
|
||||
self.messages.push(message);
|
||||
}
|
||||
|
||||
pub fn add_chat_message(&mut self, id: ClientId, username: String, message: String,) {
|
||||
self.messages.push(ChatMessage::PlayerMessage { id, username, message });
|
||||
}
|
||||
|
||||
pub fn add_player_join(&mut self, id: ClientId, username: String) {
|
||||
self.messages.push(ChatMessage::PlayerJoin { id, username });
|
||||
}
|
||||
|
||||
pub fn add_player_leave(&mut self, id: ClientId, username: String) {
|
||||
self.messages.push(ChatMessage::PlayerLeave { id, username });
|
||||
}
|
||||
|
||||
pub fn add_system_message(&mut self, message: String) {
|
||||
self.messages.push(ChatMessage::System(message));
|
||||
}
|
||||
|
||||
pub fn get_messages(&self) -> &[ChatMessage] {
|
||||
&self.messages[..]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_chat_manager(
|
||||
storages: AllStoragesView,
|
||||
) {
|
||||
let mut chat_manager = ChatHistory::default();
|
||||
chat_manager.add_system_message("Welcome to Kubi! Chat messages will appear here".to_string());
|
||||
storages.add_unique(chat_manager);
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
use shipyard::{AllStoragesView, Unique, NonSendSync, UniqueView, UniqueViewMut};
|
||||
use crate::rendering::Renderer;
|
||||
use winit::window::CursorGrabMode;
|
||||
use shipyard::{AllStoragesView, IntoIter, NonSendSync, Unique, UniqueView, UniqueViewMut, View};
|
||||
use crate::{events::InputDeviceEvent, rendering::Renderer};
|
||||
use winit::{
|
||||
event::{DeviceEvent, ElementState, RawKeyEvent},
|
||||
keyboard::{KeyCode, PhysicalKey},
|
||||
window::CursorGrabMode
|
||||
};
|
||||
|
||||
#[derive(Unique)]
|
||||
pub struct CursorLock(pub bool);
|
||||
|
@ -34,3 +38,18 @@ pub fn lock_cursor_now(
|
|||
) {
|
||||
lock.0 = true
|
||||
}
|
||||
|
||||
/// XXX: this is a huge hack
|
||||
pub fn debug_toggle_lock(
|
||||
mut lock: UniqueViewMut<CursorLock>,
|
||||
device_events: View<InputDeviceEvent>,
|
||||
) {
|
||||
for evt in device_events.iter() {
|
||||
if let DeviceEvent::Key(RawKeyEvent {
|
||||
physical_key: PhysicalKey::Code(KeyCode::F3),
|
||||
state: ElementState::Pressed,
|
||||
}) = evt.event {
|
||||
lock.0 = !lock.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,9 @@ pub(crate) use kubi_shared::transform;
|
|||
mod ui {
|
||||
pub(crate) mod loading_screen;
|
||||
pub(crate) mod connecting_screen;
|
||||
pub(crate) mod chat_ui;
|
||||
}
|
||||
pub(crate) use ui::{loading_screen, connecting_screen};
|
||||
pub(crate) use ui::{loading_screen, connecting_screen, chat_ui};
|
||||
|
||||
pub(crate) mod rendering;
|
||||
pub(crate) mod world;
|
||||
|
@ -42,6 +43,7 @@ pub(crate) mod color;
|
|||
pub(crate) mod fixed_timestamp;
|
||||
pub(crate) mod filesystem;
|
||||
pub(crate) mod client_physics;
|
||||
pub(crate) mod chat;
|
||||
|
||||
use world::{
|
||||
init_game_world,
|
||||
|
@ -76,7 +78,7 @@ use rendering::{
|
|||
};
|
||||
use block_placement::update_block_placement;
|
||||
use delta_time::{DeltaTime, init_delta_time};
|
||||
use cursor_lock::{insert_lock_state, update_cursor_lock_state, lock_cursor_now};
|
||||
use cursor_lock::{debug_toggle_lock, insert_lock_state, lock_cursor_now, update_cursor_lock_state};
|
||||
use control_flow::{exit_on_esc, insert_control_flow_unique, RequestExit};
|
||||
use state::{is_ingame, is_ingame_or_loading, is_loading, init_state, update_state, is_connecting};
|
||||
use networking::{update_networking, update_networking_late, is_multiplayer, disconnect_on_exit, is_singleplayer};
|
||||
|
@ -87,6 +89,8 @@ use connecting_screen::update_connecting_screen;
|
|||
use fixed_timestamp::init_fixed_timestamp_storage;
|
||||
use filesystem::AssetManager;
|
||||
use client_physics::{init_client_physics, update_client_physics_late};
|
||||
use chat_ui::render_chat;
|
||||
use chat::init_chat_manager;
|
||||
|
||||
/// stuff required to init the renderer and other basic systems
|
||||
fn pre_startup() -> Workload {
|
||||
|
@ -111,11 +115,13 @@ fn startup() -> Workload {
|
|||
insert_control_flow_unique,
|
||||
init_delta_time,
|
||||
init_client_physics,
|
||||
init_chat_manager,
|
||||
).into_sequential_workload()
|
||||
}
|
||||
|
||||
fn update() -> Workload {
|
||||
(
|
||||
debug_toggle_lock,
|
||||
update_window_size,
|
||||
update_cursor_lock_state,
|
||||
process_inputs,
|
||||
|
@ -144,6 +150,7 @@ fn update() -> Workload {
|
|||
update_raycasts,
|
||||
update_block_placement,
|
||||
apply_queued_blocks,
|
||||
render_chat,
|
||||
).into_sequential_workload().run_if(is_ingame),
|
||||
update_networking_late.run_if(is_multiplayer),
|
||||
compute_cameras,
|
||||
|
|
|
@ -5,9 +5,21 @@ use kubi_shared::networking::{
|
|||
state::ClientJoinState,
|
||||
channels::Channel,
|
||||
};
|
||||
use crate::player::{spawn_local_player_multiplayer, spawn_remote_player_multiplayer};
|
||||
use rand::prelude::*;
|
||||
use crate::{chat::ChatHistory, player::{spawn_local_player_multiplayer, spawn_remote_player_multiplayer}};
|
||||
use super::{UdpClient, NetworkEvent};
|
||||
|
||||
const USERNAME_BANK: &[&str] = &[
|
||||
"XxX-FishFucker-69420",
|
||||
"Sbeve34",
|
||||
"ShadowBladeX",
|
||||
"CyberNinja92",
|
||||
"sputnik1",
|
||||
"dumbpotato",
|
||||
"FortNiteNinja",
|
||||
"MinecraftMiner",
|
||||
];
|
||||
|
||||
#[derive(Unique)]
|
||||
pub struct ConnectionRejectionReason {
|
||||
pub reason: String,
|
||||
|
@ -23,7 +35,8 @@ pub fn set_client_join_state_to_connected(
|
|||
pub fn say_hello(
|
||||
mut client: UniqueViewMut<UdpClient>,
|
||||
) {
|
||||
let username = "XxX-FishFucker-69420-XxX".into();
|
||||
let mut rng = thread_rng();
|
||||
let username = (*USERNAME_BANK.choose(&mut rng).unwrap()).to_owned();
|
||||
let password = None;
|
||||
log::info!("Authenticating");
|
||||
client.0.send(
|
||||
|
@ -64,7 +77,10 @@ pub fn check_server_hello_response(
|
|||
// direction: Quat,
|
||||
// health: Health,
|
||||
// }
|
||||
|
||||
|
||||
let client_id = init.user.client_id;
|
||||
let username = init.user.username.clone();
|
||||
|
||||
//Add components to main player
|
||||
spawn_local_player_multiplayer(&mut storages, init.user);
|
||||
|
||||
|
@ -78,6 +94,10 @@ pub fn check_server_hello_response(
|
|||
*join_state = ClientJoinState::Joined;
|
||||
|
||||
log::info!("Joined the server!");
|
||||
|
||||
// Send chat message
|
||||
let mut chat = storages.borrow::<UniqueViewMut<ChatHistory>>().unwrap();
|
||||
chat.add_player_join(client_id, username);
|
||||
}
|
||||
|
||||
pub fn check_server_fuck_off_response(
|
||||
|
|
|
@ -4,12 +4,13 @@ use uflow::{SendMode, client::Event as ClientEvent};
|
|||
use kubi_shared::{
|
||||
transform::Transform,
|
||||
networking::{
|
||||
messages::{ClientToServerMessage, ServerToClientMessage, ServerToClientMessageType},
|
||||
channels::Channel,
|
||||
client::ClientIdMap,
|
||||
client::{ClientIdMap, Username},
|
||||
messages::{ClientToServerMessage, ServerToClientMessage, ServerToClientMessageType},
|
||||
},
|
||||
};
|
||||
use crate::{
|
||||
chat::ChatHistory,
|
||||
events::player_actions::PlayerActionEvent,
|
||||
player::spawn_remote_player_multiplayer,
|
||||
};
|
||||
|
@ -96,6 +97,9 @@ pub fn receive_player_connect_events(
|
|||
for message in messages {
|
||||
let ServerToClientMessage::PlayerConnected { init } = message else { unreachable!() };
|
||||
log::info!("player connected: {} (id {})", init.username, init.client_id);
|
||||
let mut chat = storages.borrow::<UniqueViewMut<ChatHistory>>().unwrap();
|
||||
chat.add_player_join(init.client_id, init.username.clone());
|
||||
drop(chat);
|
||||
spawn_remote_player_multiplayer(&mut storages, init);
|
||||
}
|
||||
}
|
||||
|
@ -120,12 +124,21 @@ pub fn receive_player_disconnect_events(
|
|||
for message in messages {
|
||||
let ServerToClientMessage::PlayerDisconnected { id } = message else { unreachable!() };
|
||||
log::info!("player disconnected: {}", id);
|
||||
|
||||
let mut id_map = storages.borrow::<UniqueViewMut<ClientIdMap>>().unwrap();
|
||||
let Some(ent_id) = id_map.0.remove(&id) else {
|
||||
log::warn!("Disconnected player entity not found in client-id map");
|
||||
continue
|
||||
};
|
||||
|
||||
let username = storages.get::<&Username>(ent_id).unwrap();
|
||||
let mut chat = storages.borrow::<UniqueViewMut<ChatHistory>>().unwrap();
|
||||
chat.add_player_leave(id, username.0.to_string());
|
||||
|
||||
drop(chat);
|
||||
drop(id_map);
|
||||
drop(username);
|
||||
|
||||
if !storages.delete_entity(ent_id) {
|
||||
log::warn!("Disconnected player entity not found in storage");
|
||||
}
|
||||
|
|
43
kubi/src/ui/chat_ui.rs
Normal file
43
kubi/src/ui/chat_ui.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use hui::{color, element::{container::Container, text::Text, UiElementExt}, layout::Alignment, size};
|
||||
use shipyard::{NonSendSync, UniqueView, UniqueViewMut};
|
||||
use crate::{chat::{ChatHistory, ChatMessage}, hui_integration::UiState, rendering::WindowSize};
|
||||
|
||||
pub fn render_chat(
|
||||
mut hui: NonSendSync<UniqueViewMut<UiState>>,
|
||||
size: UniqueView<WindowSize>,
|
||||
chat: UniqueView<ChatHistory>,
|
||||
) {
|
||||
let messages = chat.get_messages();
|
||||
if messages.is_empty() { return }
|
||||
Container::default()
|
||||
.with_size(size!(100%, 100%))
|
||||
.with_align((Alignment::Begin, Alignment::End))
|
||||
.with_children(|ui| {
|
||||
for message in messages.iter().rev().take(10).rev() {
|
||||
let (text, color) = match message {
|
||||
ChatMessage::PlayerMessage { username, id, message } => {
|
||||
(format!("{username} ({id}): {message}"), color::CYAN)
|
||||
}
|
||||
ChatMessage::PlayerJoin { username, id } => {
|
||||
(format!("{username} ({id}) joined the game"), color::YELLOW)
|
||||
}
|
||||
ChatMessage::PlayerLeave { username, id } => {
|
||||
(format!("{username} ({id}) left the game"), color::YELLOW)
|
||||
}
|
||||
ChatMessage::System(message) => {
|
||||
(message.clone(), color::WHITE)
|
||||
}
|
||||
};
|
||||
Container::default()
|
||||
.with_background((0., 0., 0., 0.5))
|
||||
.with_padding((5., 2.))
|
||||
.with_children(|ui| {
|
||||
Text::new(text)
|
||||
.with_color(color)
|
||||
.add_child(ui)
|
||||
})
|
||||
.add_child(ui);
|
||||
}
|
||||
})
|
||||
.add_root(&mut hui.hui, size.0.as_vec2());
|
||||
}
|
Loading…
Reference in a new issue