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