diff --git a/src/api.rs b/src/api.rs index 75eb953..9dffca0 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,8 +1,39 @@ #![allow(dead_code)] -use serde::{Deserialize, Serialize}; +use serde::{de, Deserialize, Serialize}; use std::collections::HashMap; +fn deser_string_to_boolean<'de, D>(deserializer: D) -> Result +where + D: de::Deserializer<'de>, +{ + let s: &str = de::Deserialize::deserialize(deserializer)?; + + match s { + "True" => Ok(true), + "False" => Ok(false), + _ => Err(de::Error::unknown_variant(s, &["True", "False"])), + } +} + +fn deser_string_to_i64<'de, D>(deserializer: D) -> Result +where + D: de::Deserializer<'de>, +{ + let s: &str = de::Deserialize::deserialize(deserializer)?; + + s.parse::().map_err(de::Error::custom) +} + +fn deser_string_to_f64<'de, D>(deserializer: D) -> Result +where + D: de::Deserializer<'de>, +{ + let s: &str = de::Deserialize::deserialize(deserializer)?; + + s.parse::().map_err(de::Error::custom) +} + #[derive(Default, Serialize)] pub struct Request { pub function: String, @@ -36,3 +67,112 @@ pub struct ServerGameState { pub average_tick_rate: f64, pub auto_load_session_name: String, } + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetServerOptions { + pub server_options: ServerOptions, + pub pending_server_options: ServerOptions, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ServerOptions { + #[serde( + deserialize_with = "deser_string_to_boolean", + rename = "FG.DSAutoPause" + )] + pub auto_pause: bool, + #[serde( + deserialize_with = "deser_string_to_boolean", + rename = "FG.DSAutoSaveOnDisconnect" + )] + pub autosave_on_disconnect: bool, + #[serde( + deserialize_with = "deser_string_to_f64", + rename = "FG.AutosaveInterval" + )] + pub autosave_interval: f64, + #[serde( + deserialize_with = "deser_string_to_f64", + rename = "FG.ServerRestartTimeSlot" + )] + pub server_restart_time_slot: f64, + #[serde( + deserialize_with = "deser_string_to_boolean", + rename = "FG.SendGameplayData" + )] + pub send_gameplay_data: bool, + #[serde(deserialize_with = "deser_string_to_i64", rename = "FG.NetworkQuality")] + pub network_quality: i64, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetAdvancedGameSettings { + pub creative_mode_enabled: bool, + pub advanced_game_settings: AdvancedGameSettings, +} + +#[derive(Debug, Deserialize)] +pub struct AdvancedGameSettings { + #[serde( + deserialize_with = "deser_string_to_boolean", + rename = "FG.GameRules.NoPower" + )] + pub game_rules_no_power: bool, + #[serde( + deserialize_with = "deser_string_to_boolean", + rename = "FG.GameRules.DisableArachnidCreatures" + )] + pub game_rules_disable_arachnid_creatures: bool, + #[serde( + deserialize_with = "deser_string_to_boolean", + rename = "FG.GameRules.NoUnlockCost" + )] + pub game_rules_no_unlock_cost: bool, + #[serde( + deserialize_with = "deser_string_to_i64", + rename = "FG.GameRules.SetGamePhase" + )] + pub game_rules_set_game_phase: i64, + #[serde( + deserialize_with = "deser_string_to_boolean", + rename = "FG.GameRules.GiveAllTiers" + )] + pub game_rules_give_all_tiers: bool, + #[serde( + deserialize_with = "deser_string_to_boolean", + rename = "FG.GameRules.UnlockAllResearchSchematics" + )] + pub game_rules_unlock_all_research_schematics: bool, + #[serde( + deserialize_with = "deser_string_to_boolean", + rename = "FG.GameRules.UnlockInstantAltRecipes" + )] + pub game_rules_unlock_instant_alt_recipes: bool, + #[serde( + deserialize_with = "deser_string_to_boolean", + rename = "FG.GameRules.UnlockAllResourceSinkSchematics" + )] + pub game_rules_unlock_all_resource_sink_schematics: bool, + #[serde( + deserialize_with = "deser_string_to_boolean", + rename = "FG.GameRules.GiveItems" + )] + pub game_rules_give_items: bool, + #[serde( + deserialize_with = "deser_string_to_boolean", + rename = "FG.PlayerRules.NoBuildCost" + )] + pub player_rules_no_build_cost: bool, + #[serde( + deserialize_with = "deser_string_to_boolean", + rename = "FG.PlayerRules.GodMode" + )] + pub player_rules_god_mode: bool, + #[serde( + deserialize_with = "deser_string_to_boolean", + rename = "FG.PlayerRules.FlightMode" + )] + pub player_rules_flight_mode: bool, +} diff --git a/src/main.rs b/src/main.rs index 2bb9774..249c5bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -145,7 +145,7 @@ async fn root() -> &'static str { } async fn metrics_handler(State(state): State>) -> Result { - let res = state + let server_game_state = state .client .post(format!("https://{}/api/v1", state.address)) .json(&api::Request { @@ -162,26 +162,51 @@ async fn metrics_handler(State(state): State>) -> Result>() + .await? + .data + .server_options; let mut buffer = String::new(); encode(&mut buffer, &state.registry).unwrap(); diff --git a/src/metrics.rs b/src/metrics.rs index 60e99c4..7be8ee4 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -1,11 +1,12 @@ +use doc_consts::DocConsts; use prometheus_client::{ metrics::gauge::Gauge, registry::{Registry, Unit}, }; use std::sync::{atomic::AtomicU64, Arc}; -#[derive(Default, Debug, doc_consts::DocConsts)] -pub struct Metrics { +#[derive(Default, Debug)] +pub struct ServerState { /// Number of the players currently connected to the Dedicated Server. pub num_connected_players: Gauge, /// Maximum Tech Tier of all Schematics currently unlocked. @@ -20,41 +21,222 @@ pub struct Metrics { pub average_tick_rate: Gauge, } +#[derive(Default, Debug)] +pub struct ServerOptions { + /// Whether the server auto-pauses when the last player disconnects. + pub auto_pause: Gauge, + /// Whether the server auto-saves when the last player disconnects. + pub autosave_on_disconnect: Gauge, + /// The interval between auto-saves. + pub autosave_interval: Gauge, + /// The timeslot the server can use for auto-restarts. + pub server_restart_time_slot: Gauge, + /// Whether the server is allowed to send gameplay data. + pub send_gameplay_data: Gauge, + /// Network quality reported by the server. + pub network_quality: Gauge, +} + +#[derive(Default, Debug)] +pub struct AdvancedGameSettings { + /// `1` if 'Advanced Game Settings' are enabled for the currently loaded session. + pub creative_mode_enabled: Gauge, + /// Whether the game rules allow no power consumption. + pub game_rules_no_power: Gauge, + /// Whether arachnid creatures are enabled. + pub game_rules_disable_arachnid_creatures: Gauge, + /// Whether the game rules allow no unlock costs. + pub game_rules_no_unlock_cost: Gauge, + /// Whether the phase of the game was set via the game rules. + pub game_rules_set_game_phase: Gauge, + /// Whether the game rules granted all game progression tiers. + pub game_rules_give_all_tiers: Gauge, + /// Whether the game rules granted all research unlocked. + pub game_rules_unlock_all_research_schematics: Gauge, + /// Whether the game rules granted all alternative recipes. + pub game_rules_unlock_instant_alt_recipes: Gauge, + /// Whether the game rules granted all Resource Sink schematics. + pub game_rules_unlock_all_resource_sink_schematics: Gauge, + /// Whether the game rules allow spawning items. + pub game_rules_give_items: Gauge, + /// Whether the game rules allow building at no item cost. + pub player_rules_no_build_cost: Gauge, + /// Whether god mode is enabled for all players. + pub player_rules_god_mode: Gauge, + /// Whether flight mode is enabled for all players. + pub player_rules_flight_mode: Gauge, +} + +#[derive(Default, Debug, DocConsts)] +pub struct Metrics { + server_state: ServerState, + server_options: ServerOptions, + advanced_game_settings: AdvancedGameSettings, +} + pub fn create_registry() -> (Registry, Arc) { let mut registry = Registry::with_prefix("satisfactory"); let metrics = Arc::new(Metrics::default()); - let subreg = registry.sub_registry_with_prefix("server_state"); - subreg.register( + let server_state = registry.sub_registry_with_prefix("server_state"); + server_state.register( "num_connected_players", - Metrics::get_docs().num_connected_players, - metrics.num_connected_players.clone(), + ServerState::get_docs().num_connected_players, + metrics.server_state.num_connected_players.clone(), ); - subreg.register( + server_state.register( "tech_tier", - Metrics::get_docs().tech_tier, - metrics.tech_tier.clone(), + ServerState::get_docs().tech_tier, + metrics.server_state.tech_tier.clone(), ); - subreg.register( + server_state.register( "is_game_running", - Metrics::get_docs().is_game_running, - metrics.is_game_running.clone(), + ServerState::get_docs().is_game_running, + metrics.server_state.is_game_running.clone(), ); - subreg.register_with_unit( + server_state.register_with_unit( "total_game_duration", - Metrics::get_docs().total_game_duration, + ServerState::get_docs().total_game_duration, Unit::Seconds, - metrics.total_game_duration.clone(), + metrics.server_state.total_game_duration.clone(), ); - subreg.register( + server_state.register( "is_game_paused", - Metrics::get_docs().is_game_paused, - metrics.is_game_paused.clone(), + ServerState::get_docs().is_game_paused, + metrics.server_state.is_game_paused.clone(), ); - subreg.register( + server_state.register( "average_tick_rate", - Metrics::get_docs().average_tick_rate, - metrics.average_tick_rate.clone(), + ServerState::get_docs().average_tick_rate, + metrics.server_state.average_tick_rate.clone(), + ); + + let server_options = registry.sub_registry_with_prefix("server_options"); + server_options.register( + "auto_pause", + ServerOptions::get_docs().auto_pause, + metrics.server_options.auto_pause.clone(), + ); + server_options.register( + "autosave_on_disconnect", + ServerOptions::get_docs().autosave_on_disconnect, + metrics.server_options.autosave_on_disconnect.clone(), + ); + server_options.register_with_unit( + "autosave_interval", + ServerOptions::get_docs().autosave_interval, + Unit::Seconds, + metrics.server_options.autosave_interval.clone(), + ); + server_options.register_with_unit( + "server_restart_time_slot", + ServerOptions::get_docs().server_restart_time_slot, + Unit::Seconds, + metrics.server_options.server_restart_time_slot.clone(), + ); + server_options.register( + "send_gameplay_data", + ServerOptions::get_docs().send_gameplay_data, + metrics.server_options.send_gameplay_data.clone(), + ); + server_options.register( + "network_quality", + ServerOptions::get_docs().network_quality, + metrics.server_options.network_quality.clone(), + ); + + let advanced_game_settings = registry.sub_registry_with_prefix("advanced_game_settings"); + advanced_game_settings.register( + "creative_mode_enabled", + AdvancedGameSettings::get_docs().creative_mode_enabled, + metrics.advanced_game_settings.creative_mode_enabled.clone(), + ); + advanced_game_settings.register( + "game_rules_no_power", + AdvancedGameSettings::get_docs().game_rules_no_power, + metrics.advanced_game_settings.game_rules_no_power.clone(), + ); + advanced_game_settings.register( + "game_rules_disable_arachnid_creatures", + AdvancedGameSettings::get_docs().game_rules_disable_arachnid_creatures, + metrics + .advanced_game_settings + .game_rules_disable_arachnid_creatures + .clone(), + ); + advanced_game_settings.register( + "game_rules_no_unlock_cost", + AdvancedGameSettings::get_docs().game_rules_no_unlock_cost, + metrics + .advanced_game_settings + .game_rules_no_unlock_cost + .clone(), + ); + advanced_game_settings.register( + "game_rules_set_game_phase", + AdvancedGameSettings::get_docs().game_rules_set_game_phase, + metrics + .advanced_game_settings + .game_rules_set_game_phase + .clone(), + ); + advanced_game_settings.register( + "game_rules_give_all_tiers", + AdvancedGameSettings::get_docs().game_rules_give_all_tiers, + metrics + .advanced_game_settings + .game_rules_give_all_tiers + .clone(), + ); + advanced_game_settings.register( + "game_rules_unlock_all_research_schematics", + AdvancedGameSettings::get_docs().game_rules_unlock_all_research_schematics, + metrics + .advanced_game_settings + .game_rules_unlock_all_research_schematics + .clone(), + ); + advanced_game_settings.register( + "game_rules_unlock_instant_alt_recipes", + AdvancedGameSettings::get_docs().game_rules_unlock_instant_alt_recipes, + metrics + .advanced_game_settings + .game_rules_unlock_instant_alt_recipes + .clone(), + ); + advanced_game_settings.register( + "game_rules_unlock_all_resource_sink_schematics", + AdvancedGameSettings::get_docs().game_rules_unlock_all_resource_sink_schematics, + metrics + .advanced_game_settings + .game_rules_unlock_all_resource_sink_schematics + .clone(), + ); + advanced_game_settings.register( + "game_rules_give_items", + AdvancedGameSettings::get_docs().game_rules_give_items, + metrics.advanced_game_settings.game_rules_give_items.clone(), + ); + advanced_game_settings.register( + "player_rules_no_build_cost", + AdvancedGameSettings::get_docs().player_rules_no_build_cost, + metrics + .advanced_game_settings + .player_rules_no_build_cost + .clone(), + ); + advanced_game_settings.register( + "player_rules_god_mode", + AdvancedGameSettings::get_docs().player_rules_god_mode, + metrics.advanced_game_settings.player_rules_god_mode.clone(), + ); + advanced_game_settings.register( + "player_rules_flight_mode", + AdvancedGameSettings::get_docs().player_rules_flight_mode, + metrics + .advanced_game_settings + .player_rules_flight_mode + .clone(), ); (registry, metrics)