mirror of
https://github.com/IntQuant/noita_entangled_worlds.git
synced 2025-10-19 07:03:16 +00:00
Display player avatars in proxy
This commit is contained in:
parent
86f1ed7e24
commit
ed8959fb36
5 changed files with 194 additions and 86 deletions
|
@ -1,5 +1,5 @@
|
|||
use std::{
|
||||
env, fmt::Display, net::SocketAddr, sync::{atomic::Ordering, Arc}, thread, time::Duration
|
||||
fmt::Display, net::SocketAddr, sync::{atomic::Ordering, Arc}, time::Duration
|
||||
};
|
||||
|
||||
use bitcode::{Decode, Encode};
|
||||
|
@ -17,6 +17,8 @@ pub mod messages;
|
|||
mod mod_manager;
|
||||
mod self_update;
|
||||
pub mod releases;
|
||||
pub mod net;
|
||||
pub mod steam_helper;
|
||||
|
||||
#[derive(Debug, Decode, Encode, Clone)]
|
||||
pub struct GameSettings {
|
||||
|
@ -24,8 +26,6 @@ pub struct GameSettings {
|
|||
debug_mode: bool,
|
||||
}
|
||||
|
||||
pub mod net;
|
||||
|
||||
enum AppState {
|
||||
Connect,
|
||||
ModManager,
|
||||
|
@ -34,28 +34,6 @@ enum AppState {
|
|||
SelfUpdate,
|
||||
}
|
||||
|
||||
struct SteamState {
|
||||
pub client: steamworks::Client,
|
||||
}
|
||||
|
||||
impl SteamState {
|
||||
fn new() -> Result<Self, SteamAPIInitError> {
|
||||
if env::var_os("NP_DISABLE_STEAM").is_some() {
|
||||
return Err(SteamAPIInitError::FailedGeneric("Disabled by env variable".to_string()))
|
||||
}
|
||||
let app_id = env::var("NP_APPID").ok().and_then(|x| x.parse().ok());
|
||||
let (client, single) = steamworks::Client::init_app(app_id.unwrap_or(881100))?;
|
||||
thread::spawn(move || {
|
||||
info!("Spawned steam callback thread");
|
||||
loop {
|
||||
single.run_callbacks();
|
||||
thread::sleep(Duration::from_millis(3));
|
||||
}
|
||||
});
|
||||
Ok(SteamState { client })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct AppSavedState {
|
||||
addr: String,
|
||||
|
@ -76,7 +54,7 @@ impl Default for AppSavedState {
|
|||
pub struct App {
|
||||
state: AppState,
|
||||
modmanager: Modmanager,
|
||||
steam_state: Result<SteamState, SteamAPIInitError>,
|
||||
steam_state: Result<steam_helper::SteamState, SteamAPIInitError>,
|
||||
saved_state: AppSavedState,
|
||||
modmanager_settings: ModmanagerSettings,
|
||||
self_update: SelfUpdateManager,
|
||||
|
@ -95,7 +73,7 @@ impl App {
|
|||
Self {
|
||||
state: AppState::ModManager,
|
||||
modmanager: Modmanager::default(),
|
||||
steam_state: SteamState::new(),saved_state,
|
||||
steam_state: steam_helper::SteamState::new(),saved_state,
|
||||
modmanager_settings,
|
||||
self_update: SelfUpdateManager::new(),
|
||||
}
|
||||
|
@ -151,6 +129,57 @@ impl App {
|
|||
netman.clone().start();
|
||||
self.state = AppState::Netman { netman };
|
||||
}
|
||||
|
||||
fn connect_screen(&mut self, ctx: &egui::Context) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("Noita Entangled Worlds proxy");
|
||||
ui.checkbox(&mut self.saved_state.debug_mode, "Debug mode");
|
||||
ui.checkbox(&mut self.saved_state.use_constant_seed, "Use specified seed");
|
||||
if ui.button("Host").clicked() {
|
||||
self.start_server();
|
||||
}
|
||||
ui.separator();
|
||||
ui.text_edit_singleline(&mut self.saved_state.addr);
|
||||
let addr = self.saved_state.addr.parse();
|
||||
ui.add_enabled_ui(addr.is_ok(), |ui| {
|
||||
if ui.button("Connect").clicked() {
|
||||
if let Ok(addr) = addr {
|
||||
self.start_connect(addr);
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.separator();
|
||||
ui.heading("Steam networking");
|
||||
match &self.steam_state {
|
||||
Ok(_) => {
|
||||
if ui.button("Create lobby").clicked() {
|
||||
self.start_steam_host();
|
||||
}
|
||||
if ui.button("Connect to lobby in clipboard").clicked() {
|
||||
let id = ClipboardProvider::new()
|
||||
.and_then(|mut ctx: ClipboardContext| ctx.get_contents());
|
||||
match id {
|
||||
Ok(id) => {
|
||||
let id = id.parse().map(LobbyId::from_raw);
|
||||
match id {
|
||||
Ok(id) => self.start_steam_connect(id),
|
||||
Err(error) => self.notify_error(error),
|
||||
}
|
||||
}
|
||||
Err(error) => self.notify_error(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
ui.label(format!("Could not init steam networking: {}", err));
|
||||
}
|
||||
}
|
||||
self.self_update.display_version(ui);
|
||||
if self.self_update.request_update {
|
||||
self.state = AppState::SelfUpdate;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for App {
|
||||
|
@ -158,59 +187,34 @@ impl eframe::App for App {
|
|||
ctx.request_repaint_after(Duration::from_secs(1));
|
||||
match &self.state {
|
||||
AppState::Connect => {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("Noita Entangled Worlds proxy");
|
||||
ui.checkbox(&mut self.saved_state.debug_mode, "Debug mode");
|
||||
ui.checkbox(&mut self.saved_state.use_constant_seed, "Use specified seed");
|
||||
if ui.button("Host").clicked() {
|
||||
self.start_server();
|
||||
}
|
||||
ui.separator();
|
||||
ui.text_edit_singleline(&mut self.saved_state.addr);
|
||||
let addr = self.saved_state.addr.parse();
|
||||
ui.add_enabled_ui(addr.is_ok(), |ui| {
|
||||
if ui.button("Connect").clicked() {
|
||||
if let Ok(addr) = addr {
|
||||
self.start_connect(addr);
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.separator();
|
||||
ui.heading("Steam networking");
|
||||
match &self.steam_state {
|
||||
Ok(_) => {
|
||||
if ui.button("Create lobby").clicked() {
|
||||
self.start_steam_host();
|
||||
}
|
||||
if ui.button("Connect to lobby in clipboard").clicked() {
|
||||
let id = ClipboardProvider::new()
|
||||
.and_then(|mut ctx: ClipboardContext| ctx.get_contents());
|
||||
match id {
|
||||
Ok(id) => {
|
||||
let id = id.parse().map(LobbyId::from_raw);
|
||||
match id {
|
||||
Ok(id) => self.start_steam_connect(id),
|
||||
Err(error) => self.notify_error(error),
|
||||
}
|
||||
}
|
||||
Err(error) => self.notify_error(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
ui.label(format!("Could not init steam networking: {}", err));
|
||||
}
|
||||
}
|
||||
self.self_update.display_version(ui);
|
||||
if self.self_update.request_update {
|
||||
self.state = AppState::SelfUpdate;
|
||||
}
|
||||
});
|
||||
self.connect_screen(ctx);
|
||||
}
|
||||
AppState::Netman { netman } => {
|
||||
let stopped = netman.stopped.load(Ordering::Relaxed);
|
||||
let accept_local = netman.accept_local.load(Ordering::Relaxed);
|
||||
let local_connected = netman.local_connected.load(Ordering::Relaxed);
|
||||
egui::SidePanel::left("players").resizable(false).exact_width(200.0).show(ctx, |ui| {
|
||||
ui.heading("Players");
|
||||
if netman.peer.is_steam() {
|
||||
let steam = self.steam_state.as_mut().expect("steam should be available, as we are using steam networking");
|
||||
for peer in netman.peer.iter_peer_ids() {
|
||||
let role = peer_role(peer, netman);
|
||||
|
||||
let username = steam.get_user_name(peer.into());
|
||||
let avatar = steam.get_avatar(ctx, peer.into());
|
||||
if let Some(avatar) = avatar {
|
||||
avatar.display_with_labels(ui, &username, role);
|
||||
ui.add_space(5.0);
|
||||
} else {
|
||||
ui.label(&username);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for peer in netman.peer.iter_peer_ids() {
|
||||
ui.label(peer.to_string());
|
||||
}
|
||||
}
|
||||
});
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if stopped {
|
||||
ui.colored_label(Color32::LIGHT_RED, "Netmanager thread has stopped");
|
||||
|
@ -231,17 +235,15 @@ impl eframe::App for App {
|
|||
ui.label("Not yet ready");
|
||||
}
|
||||
ui.separator();
|
||||
if let Some(id) = netman.peer.lobby_id() {
|
||||
if ui.button("Save lobby id to clipboard").clicked() {
|
||||
let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
|
||||
let _ = ctx.set_contents(id.raw().to_string());
|
||||
if netman.peer.is_steam() {
|
||||
if let Some(id) = netman.peer.lobby_id() {
|
||||
if ui.button("Save lobby id to clipboard").clicked() {
|
||||
let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
|
||||
let _ = ctx.set_contents(id.raw().to_string());
|
||||
}
|
||||
} else {
|
||||
ui.label("Lobby id not available");
|
||||
}
|
||||
} else {
|
||||
ui.label("Lobby id not available");
|
||||
}
|
||||
ui.heading("Current users");
|
||||
for peer in netman.peer.iter_peer_ids() {
|
||||
ui.label(peer.to_string());
|
||||
}
|
||||
ui.label(format!("Peer state: {}", netman.peer.state()));
|
||||
});
|
||||
|
@ -281,3 +283,15 @@ impl eframe::App for App {
|
|||
eframe::set_value(storage, MODMANAGER, &self.modmanager_settings);
|
||||
}
|
||||
}
|
||||
|
||||
fn peer_role(peer: net::omni::OmniPeerId, netman: &Arc<net::NetManager>) -> &str {
|
||||
if peer == netman.peer.host_id() {
|
||||
"Host"
|
||||
} else {
|
||||
if Some(peer) == netman.peer.my_id() {
|
||||
"Me"
|
||||
} else {
|
||||
"Player"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use tracing::{error, info};
|
|||
|
||||
use crate::{
|
||||
releases::{get_release_by_tag, Downloader, ReleasesError, Version},
|
||||
SteamState,
|
||||
steam_helper::SteamState,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
@ -133,4 +133,11 @@ impl PeerVariant {
|
|||
PeerVariant::Steam(p) => p.lobby_id(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_steam(&self) -> bool {
|
||||
match self {
|
||||
PeerVariant::Steam(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
86
noita-proxy/src/steam_helper.rs
Normal file
86
noita-proxy/src/steam_helper.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use std::{collections::HashMap, env, thread, time::Duration};
|
||||
|
||||
use eframe::egui::{self, ColorImage, RichText, TextureHandle, TextureOptions, Ui, Widget};
|
||||
use steamworks::{SteamAPIInitError, SteamId};
|
||||
use tracing::info;
|
||||
|
||||
pub struct SteamUserAvatar {
|
||||
avatar: TextureHandle,
|
||||
}
|
||||
|
||||
impl SteamUserAvatar {
|
||||
pub fn display(&self, ui: &mut Ui) -> egui::Response {
|
||||
egui::Image::new(&self.avatar).ui(ui)
|
||||
}
|
||||
pub fn display_with_labels(&self, ui: &mut Ui, label_top: &str, label_bottom: &str) {
|
||||
let image = egui::Image::new(&self.avatar).fit_to_exact_size([32.0, 32.0].into());
|
||||
ui.group(|ui| {
|
||||
ui.set_min_width(200.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(image);
|
||||
ui.vertical(|ui| {
|
||||
ui.label(RichText::new(label_top).size(14.0));
|
||||
ui.label(RichText::new(label_bottom).size(11.0));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SteamState {
|
||||
pub client: steamworks::Client,
|
||||
avatar_cache: HashMap<SteamId, SteamUserAvatar>,
|
||||
}
|
||||
|
||||
impl SteamState {
|
||||
pub(crate) fn new() -> Result<Self, SteamAPIInitError> {
|
||||
if env::var_os("NP_DISABLE_STEAM").is_some() {
|
||||
return Err(SteamAPIInitError::FailedGeneric(
|
||||
"Disabled by env variable".to_string(),
|
||||
));
|
||||
}
|
||||
let app_id = env::var("NP_APPID").ok().and_then(|x| x.parse().ok());
|
||||
let (client, single) = steamworks::Client::init_app(app_id.unwrap_or(881100))?;
|
||||
thread::spawn(move || {
|
||||
info!("Spawned steam callback thread");
|
||||
loop {
|
||||
single.run_callbacks();
|
||||
thread::sleep(Duration::from_millis(3));
|
||||
}
|
||||
});
|
||||
Ok(SteamState {
|
||||
client,
|
||||
avatar_cache: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_user_name(&self, id: SteamId) -> String {
|
||||
let friends = self.client.friends();
|
||||
friends.get_friend(id).name()
|
||||
}
|
||||
|
||||
pub fn get_avatar(&mut self, ctx: &egui::Context, id: SteamId) -> Option<&SteamUserAvatar> {
|
||||
let friends = self.client.friends();
|
||||
|
||||
if self.avatar_cache.contains_key(&id) {
|
||||
self.avatar_cache.get(&id)
|
||||
} else {
|
||||
friends
|
||||
.get_friend(id)
|
||||
.small_avatar()
|
||||
.map(|data| {
|
||||
ctx.load_texture(
|
||||
format!("steam_avatar_for_{:?}", id),
|
||||
ColorImage::from_rgba_unmultiplied([32, 32], &data),
|
||||
TextureOptions::LINEAR,
|
||||
)
|
||||
})
|
||||
.map(|avatar| {
|
||||
let avatar = SteamUserAvatar { avatar };
|
||||
&*self.avatar_cache.entry(id).or_insert(avatar)
|
||||
})
|
||||
}
|
||||
|
||||
// ctx.load_texture(name, image, options)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue