1#![deny(missing_docs)]
2#![forbid(unsafe_code)]
3
4use std::{ops::Deref, sync::Mutex};
61
62use bevy_app::{App, First, Plugin};
63use bevy_ecs::{
64 message::Message,
65 prelude::{MessageWriter, Resource},
66 schedule::{IntoScheduleConfigs, SystemSet},
67 system::Res,
68};
69
70pub use steamworks::{
72 networking_messages, networking_sockets, networking_types, networking_utils,
73 restart_app_if_necessary, stats, AccountId, AppIDs, AppId, Apps, AuthSessionError,
74 AuthSessionTicketResponse, AuthSessionValidateError, AuthTicket, Callback, CallbackHandle,
75 CallbackResult, ChatMemberStateChange, ComparisonFilter, CreateQueryError, DistanceFilter,
76 DownloadItemResult, FileType, FloatingGamepadTextInputDismissed, FloatingGamepadTextInputMode,
77 Friend, FriendFlags, FriendGame, FriendState, Friends, GameId, GameLobbyJoinRequested,
78 GameOverlayActivated, GamepadTextInputDismissed, GamepadTextInputLineMode,
79 GamepadTextInputMode, Input, InstallInfo, InvalidErrorCode, ItemState, Leaderboard,
80 LeaderboardDataRequest, LeaderboardDisplayType, LeaderboardEntry, LeaderboardScoreUploaded,
81 LeaderboardSortMethod, LobbyChatUpdate, LobbyDataUpdate, LobbyId, LobbyKey,
82 LobbyKeyTooLongError, LobbyListFilter, LobbyType, Matchmaking, MicroTxnAuthorizationResponse,
83 NearFilter, NearFilters, Networking, NotificationPosition, NumberFilter, NumberFilters,
84 OverlayToStoreFlag, P2PSessionConnectFail, P2PSessionRequest, PersonaChange,
85 PersonaStateChange, PublishedFileId, PublishedFileVisibility, QueryHandle, QueryResult,
86 QueryResults, RemotePlay, RemotePlayConnected, RemotePlayDisconnected, RemotePlaySession,
87 RemotePlaySessionId, RemoteStorage, SIResult, SResult, SendType, Server, ServerMode,
88 SteamAPIInitError, SteamDeviceFormFactor, SteamError, SteamFile, SteamFileInfo,
89 SteamFileReader, SteamFileWriter, SteamId, SteamServerConnectFailure, SteamServersConnected,
90 SteamServersDisconnected, StringFilter, StringFilterKind, StringFilters,
91 TicketForWebApiResponse, UGCContentDescriptorID, UGCQueryType, UGCStatisticType, UGCType,
92 UpdateHandle, UpdateStatus, UpdateWatchHandle, UploadScoreMethod, User, UserAchievementStored,
93 UserList, UserListOrder, UserStats, UserStatsReceived, UserStatsStored, Utils,
94 ValidateAuthTicketResponse, RESULTS_PER_PAGE, UGC,
95};
96
97#[derive(Message, Debug)]
99#[allow(missing_docs)]
100pub enum SteamworksEvent {
101 CallbackResult(CallbackResult),
102}
103
104#[derive(Resource, Clone)]
111pub struct Client(steamworks::Client);
112
113impl Deref for Client {
114 type Target = steamworks::Client;
115 fn deref(&self) -> &Self::Target {
116 &self.0
117 }
118}
119
120pub struct SteamworksPlugin {
122 steam: Mutex<Option<steamworks::Client>>,
123}
124
125impl SteamworksPlugin {
126 pub fn init_app(app_id: impl Into<AppId>) -> Result<Self, SteamAPIInitError> {
129 Ok(Self {
130 steam: Mutex::new(Some(steamworks::Client::init_app(app_id.into())?)),
131 })
132 }
133
134 pub fn init() -> Result<Self, SteamAPIInitError> {
139 Ok(Self {
140 steam: Mutex::new(Some(steamworks::Client::init()?)),
141 })
142 }
143}
144
145impl From<steamworks::Client> for SteamworksPlugin {
146 fn from(client: steamworks::Client) -> Self {
147 Self {
148 steam: Mutex::new(Some(client)),
149 }
150 }
151}
152
153impl Plugin for SteamworksPlugin {
154 fn build(&self, app: &mut App) {
155 let client = self
156 .steam
157 .lock()
158 .unwrap()
159 .take()
160 .expect("The SteamworksPlugin was initialized more than once");
161
162 app.insert_resource(Client(client.clone()))
163 .add_message::<SteamworksEvent>()
164 .configure_sets(First, SteamworksSystem::RunCallbacks)
165 .add_systems(
166 First,
167 run_steam_callbacks
168 .in_set(SteamworksSystem::RunCallbacks)
169 .before(bevy_ecs::message::MessageUpdateSystems),
170 );
171 }
172}
173
174#[derive(Debug, Clone, Copy, Eq, Hash, SystemSet, PartialEq)]
178pub enum SteamworksSystem {
179 RunCallbacks,
183}
184
185fn run_steam_callbacks(client: Res<Client>, mut output: MessageWriter<SteamworksEvent>) {
186 client.process_callbacks(|callback| {
187 output.write(SteamworksEvent::CallbackResult(callback));
188 });
189}