C#:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins {
[Info("Our Servers", "https://topplugin.ru/", "0.0.8")]
[Description("Show information about other servers (with fetching current online)")]
public class OurServers : RustPlugin {
#region Classes
private class Server {
[JsonProperty("Name of your server in interface")]
public string DisplayName;
[JsonProperty("Description of your server in interface")]
public string Description;
[JsonProperty("IP:Port of server (ex: 127.0.0.1:12000)")]
public string IPAndPort;
[JsonProperty("Interface width (increase it, if your description is very length)")]
public int ServerWidth = 250;
[JsonIgnore]
public int CurrentOnline;
[JsonIgnore]
public int MaxOnline;
[JsonIgnore]
public bool Status = false;
public string IP() => IPAndPort.Split(':')[0];
public int Port() => int.Parse(IPAndPort.Split(':')[1]);
public int GetOnline() => Mathf.Min(MaxOnline, CurrentOnline);
public void UpdateStatus(bool status) => Status = status;
}
private class Configuration {
[JsonProperty("Servers configure")]
public List<Server> Servers = new List<Server>();
[JsonProperty("Command to open interface")]
public string Command = "servers";
[JsonProperty("Use chat instead of UI")]
public bool UseChat = false;
[JsonProperty("Information update interval in seconds (should be > 30)")]
public int Interval = 60;
[JsonProperty("Which API to use? (Possible options: steampowered or battlemetrics) (Default: battlemetrics)")]
public string API = "battlemetrics";
[JsonProperty("Steampowered API key")]
public string SteamWebApiKey = "!!! You can get it HERE > https://steamcommunity.com/dev/apikey < and you need to insert HERE !!!";
public static Configuration Generate() {
return new Configuration {
Servers = new List<Server> {
new Server {
DisplayName = "UMOD-SERVER #1 - PROCEDURAL", Description = "Example server, with example description", IPAndPort = "8.8.8.8:12000", ServerWidth = 250
}
, new Server {
DisplayName = "UMOD-SERVER #2 - BARREN", Description = "Example server, with example description", IPAndPort = "1.1.1.1:13000"
}
}
}
;
}
}
public class BattlemetricsApiResponse {
[JsonProperty("data")]
public List<ServerData> data;
public class ServerData {
[JsonProperty("id")]
public string id;
[JsonProperty("type")]
public string type;
[JsonProperty("attributes")]
public Attributes attributes;
public class Attributes {
[JsonProperty("id")]
public string id;
[JsonProperty("name")]
public string name;
[JsonProperty("ip")]
public string ip;
[JsonProperty("port")]
public int port;
[JsonProperty("portQuery")]
public int portQuery;
[JsonProperty("players")]
public int players;
[JsonProperty("maxPlayers")]
public int maxPlayers;
[JsonProperty("status")]
public string status;
}
}
}
public class SteampoweredApi {
[JsonProperty("response")]
public Response response;
public class Response {
[JsonProperty("servers")]
public List<ServerData> servers;
public class ServerData {
[JsonProperty("addr")]
public string addr;
[JsonProperty("gameport")]
public int gameport;
[JsonProperty("steamid")]
public string steamid;
[JsonProperty("name")]
public string name;
[JsonProperty("appid")]
public int appid;
[JsonProperty("gamedir")]
public string gamedir;
[JsonProperty("version")]
public string version;
[JsonProperty("product")]
public string product;
[JsonProperty("region")]
public int region;
[JsonProperty("players")]
public int players;
[JsonProperty("max_players")]
public int max_players;
[JsonProperty("bots")]
public int bots;
[JsonProperty("map")]
public string map;
[JsonProperty("secure")]
public bool secure;
[JsonProperty("dedicated")]
public bool dedicated;
[JsonProperty("os")]
public string os;
[JsonProperty("gametype")]
public string gametype;
}
}
}
#endregion
#region Variables
// Coroutines
private Coroutine UpdateAction;
// Configuration
private Configuration Settings;
private bool Initialized;
private bool Broken;
#endregion
#region Initialization
protected override void LoadConfig() {
base.LoadConfig();
try {
Settings = Config.ReadObject<Configuration>();
if (Settings?.Servers == null) LoadDefaultConfig();
SaveConfig();
if (Settings.API == "steampowered" && (Settings.SteamWebApiKey == null || Settings.SteamWebApiKey == string.Empty || Settings.SteamWebApiKey.Length != 32)) {
PrintError("Steampowered API requires an Steam Web API key to work! Check your configuration!");
Broken = true;
}
}
catch {
PrintError("Error reading config, please check!");
Broken = true;
}
}
protected override void SaveConfig() => Config.WriteObject(Settings);
protected override void LoadDefaultConfig() {
Settings = Configuration.Generate();
SaveConfig();
}
private void OnServerInitialized() {
if (Broken) return;
cmd.AddChatCommand(Settings.Command, this, nameof(CmdShowServers));
if (Settings.Interval < 30) {
PrintError($"Update interval should be bigger then 30 s!");
Settings.Interval = 30;
}
UpdateAction = ServerMgr.Instance.StartCoroutine(UpdateOnline());
}
private void Unload() {
if (UpdateAction != null) ServerMgr.Instance.StopCoroutine(UpdateAction);
}
#endregion
#region Functions
private IEnumerator UpdateOnline() {
while (true) {
foreach (var check in Settings.Servers) {
if (Settings.API == "steampowered") {
string url = $"https://api.steampowered.com/IGameServersService/GetServerList/v1/?format=json&key={Settings.SteamWebApiKey}&filter=\\gameaddr\\{check.IP()}:{check.Port()}";
webrequest.Enqueue(url, "", (code, response) => {
switch (code) {
case 200 : try {
SteampoweredApi data = JsonConvert.DeserializeObject<SteampoweredApi>(response);
if (data.response.servers != null) {
switch (data.response.servers.Count) {
case 1 : SteampoweredApi.Response.ServerData serverData = data.response.servers.Last();
if (serverData.addr != $"{check.IP()}:{check.Port()}") {
PrintError($"Steampowered API: The response from Steampowered does not match the IP or port of the expected server! (Request: {check.IP()}:{check.Port()}, Response: {serverData.addr})");
}
else {
check.CurrentOnline = serverData.players;
check.MaxOnline = serverData.max_players;
check.UpdateStatus(true);
Initialized = true;
}
break;
default : PrintError($"Steampowered API: The number of results is different than expected! Contact the plugin developer! (Server: {check.IP()}:{check.Port()}, Count: {data.response.servers.Count})");
break;
}
}
else {
check.UpdateStatus(false);
}
}
catch {
PrintError($"Steampowered API: Error parsing response from server.Perhaps the format has changed.Contact the plugin developer!");
}
break;
case 403 : PrintError($"Steampowered API: Invalid Steam Web API key! Check it with your key indicated on the page: https://steamcommunity.com/dev/apikey");
break;
default : PrintError($"Steampowered API HTTP CODE: {code}");
break;
}
}
, this);
}
else {
string url = $"https://api.battlemetrics.com/servers?filter[search]=\"{check.IP()}:{check.Port()}\"";
webrequest.Enqueue(url, "", (code, response) => {
if (code == 200) {
try {
BattlemetricsApiResponse apiResponse = JsonConvert.DeserializeObject<BattlemetricsApiResponse>(response);
switch (apiResponse.data.Count) {
case 1 : BattlemetricsApiResponse.ServerData serverData = apiResponse.data.Last();
if (check.IP() != serverData.attributes.ip || check.Port() != serverData.attributes.port) {
PrintError($"Battlemetrics API: The response from Battlemetrics does not match the IP or port of the expected server! (Request: {check.IP()}:{check.Port()}, Response: {serverData.attributes.ip}:{serverData.attributes.port})");
}
else {
check.CurrentOnline = serverData.attributes.players;
check.MaxOnline = serverData.attributes.maxPlayers;
check.UpdateStatus((serverData.attributes.status == "online"));
Initialized = true;
}
break;
case 0 : PrintError($"Battlemetrics API: Server '{check.IP()}:{check.Port()}' not found! Perhaps he has not yet been interviewed.Wait a while.");
break;
default : PrintError($"Battlemetrics API: The number of results is different than expected! Contact the plugin developer! (Server: {check.IP()}:{check.Port()}, Count: {apiResponse.data.Count})");
break;
}
}
catch {
PrintError($"Battlemetrics API: Error parsing response from server.Perhaps the format has changed.Contact the plugin developer!");
}
}
else {
PrintError($"Battlemetrics API HTTP CODE: {code}");
}
}
, this);
}
yield return new WaitForSeconds(1f);
}
yield return new WaitForSeconds(Settings.Interval);
}
}
#endregion
#region Commands
[ConsoleCommand("UI_OurServersHandler")]
private void CmdConsoleHandler(ConsoleSystem.Arg args) {
BasePlayer player = args.Player();
if (player == null || !args.HasArgs(1)) return;
switch (args.Args[0].ToLower()) {
case "show" : {
if (!args.HasArgs(2)) return;
int index = 0;
if (!int.TryParse(args.Args[1], out index) || Settings.Servers.ElementAtOrDefault(index) == null) return;
UI_DrawInterface(player, index);
break;
}
case "hide" : {
UI_DrawInterface(player, -2);
break;
}
}
}
private void CmdShowServers(BasePlayer player, string command, string[] args) {
if (!Initialized) return;
if (Settings.UseChat) {
string resultMessage = "";
foreach (var server in Settings.Servers) resultMessage += $"{server.DisplayName} (IP: {server.IPAndPort}) - " + ((server.Status) ? $"{server.GetOnline()}/{server.MaxOnline} players online" : "OFFLINE") + "\n\n";
player.ChatMessage(resultMessage);
}
else {
UI_DrawInterface(player);
}
}
#endregion
#region Interface
private const string Layer = "UI_OurServersLayer";
private void UI_DrawInterface(BasePlayer player, int index = -1) {
CuiHelper.DestroyUi(player, Layer);
CuiElementContainer container = new CuiElementContainer();
float fadeSpeed = 1f;
Server choosedServer = null;
if (index >= 0) {
fadeSpeed = 0f;
choosedServer = Settings.Servers.ElementAtOrDefault(index);
}
else if (index == -2) {
fadeSpeed = 0;
}
container.Add(new CuiPanel {
CursorEnabled = true, RectTransform = {
AnchorMin = "0 0", AnchorMax = "1 1", OffsetMax = "0 0"
}
, Image = {
FadeIn = fadeSpeed, Color = "0 0 0 0.7"
}
}
, "Overlay", Layer);
container.Add(new CuiButton {
RectTransform = {
AnchorMin = "0 0", AnchorMax = "1 1", OffsetMax = "0 0"
}
, Button = {
Color = "0 0 0 0", Close = Layer
}
, Text = {
Text = ""
}
}
, Layer);
float panelWidth = 80;
var list = Settings.Servers.Where(p => p.CurrentOnline > -1).ToList();
float topPosition = list.Count / 2f * panelWidth + (list.Count - 1) / 2f * 15;
float startPosition = 0.5f;
if (list.Count >= 6) {
startPosition = 0.25f;
topPosition /= 2f;
}
foreach (var check in list) {
float leftPosition = check.ServerWidth * -1 - 83f;
if (list.IndexOf(check) == list.Count - 1 && startPosition == 0.25f) startPosition = 0.5f;
container.Add(new CuiPanel {
RectTransform = {
AnchorMin = $"{startPosition} 0.5", AnchorMax = $"{startPosition} {0.5f}", OffsetMin = $"{leftPosition} {topPosition - panelWidth}", OffsetMax = $"{check.ServerWidth} {topPosition}"
}
, Image = {
FadeIn = fadeSpeed, Color = "1 1 1 0.03008521"
}
}
, Layer, check.IPAndPort);
if (check != choosedServer) {
container.Add(new CuiPanel {
RectTransform = {
AnchorMin = "1 0", AnchorMax = "1 1", OffsetMin = "3 0", OffsetMax = "83 0"
}
, Image = {
FadeIn = fadeSpeed, Color = "1 1 1 0.03008521"
}
}
, check.IPAndPort, check.IPAndPort + ".Help");
container.Add(new CuiButton {
RectTransform = {
AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "10 10", OffsetMax = "-10 -10"
}
, Button = {
Color = "1 1 1 0", Sprite = "assets/icons/circle_closed_toedge.png", Command = $"UI_OurServersHandler show {Settings.Servers.IndexOf(check)}"
}
, Text = {
FadeIn = fadeSpeed, Text = "i", Align = TextAnchor.MiddleCenter, Font = "robotocondensed-bold.ttf", FontSize = 50, Color = "1 1 1 0.7"
}
}
, check.IPAndPort + ".Help");
container.Add(new CuiLabel {
RectTransform = {
AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = "15 -45", OffsetMax = "-15 -10"
}
, Text = {
FadeIn = fadeSpeed, Text = check.DisplayName, Font = "robotocondensed-bold.ttf", FontSize = 20, Align = TextAnchor.LowerLeft
}
}
, check.IPAndPort);
container.Add(new CuiLabel {
RectTransform = {
AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = "15 -25", OffsetMax = "-15 -5"
}
, Text = {
FadeIn = fadeSpeed, Text = check.IPAndPort, Font = "robotocondensed-regular.ttf", FontSize = 16, Color = "1 1 1 0.6", Align = TextAnchor.MiddleLeft
}
}
, check.IPAndPort);
container.Add(new CuiLabel {
RectTransform = {
AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = "15 -45", OffsetMax = "-15 -5"
}
, Text = {
FadeIn = fadeSpeed, Text = ((check.Status) ? $"{check.GetOnline()} / {check.MaxOnline}" : "OFFLINE"), Font = "robotocondensed-regular.ttf", FontSize = 32, Color = "1 1 1 0.4", Align = TextAnchor.MiddleRight
}
}
, check.IPAndPort);
container.Add(new CuiPanel {
RectTransform = {
AnchorMin = "0 0", AnchorMax = "1 0", OffsetMin = "15 10", OffsetMax = "-15 30"
}
, Image = {
FadeIn = fadeSpeed, Color = ((check.Status) ? "1 1 1 0.4" : "1 0.2 0 0.4")
}
}
, check.IPAndPort, check.IPAndPort + ".Online");
container.Add(new CuiPanel {
RectTransform = {
AnchorMin = "0 0", AnchorMax = $"{(float) check.GetOnline() / check.MaxOnline} 1", OffsetMin = "0 0", OffsetMax = "0 0"
}
, Image = {
FadeIn = fadeSpeed, Color = "0.6 0.9 0.6 0.8"
}
}
, check.IPAndPort + ".Online");
}
else {
container.Add(new CuiPanel {
RectTransform = {
AnchorMin = "1 0", AnchorMax = "1 1", OffsetMin = "3 0", OffsetMax = "83 0"
}
, Image = {
FadeIn = fadeSpeed, Color = "1 1 1 0.03008521"
}
}
, check.IPAndPort, check.IPAndPort + ".Help");
container.Add(new CuiButton {
RectTransform = {
AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "10 10", OffsetMax = "-10 -10"
}
, Button = {
Color = "1 1 1 0", Sprite = "assets/icons/circle_closed_toedge.png", Command = $"UI_OurServersHandler hide"
}
, Text = {
FadeIn = fadeSpeed, Text = "X", Align = TextAnchor.MiddleCenter, Font = "robotocondensed-bold.ttf", FontSize = 50, Color = "1 1 1 0.7"
}
}
, check.IPAndPort + ".Help");
container.Add(new CuiLabel {
RectTransform = {
AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "15 15", OffsetMax = "-15 -15"
}
, Text = {
FadeIn = fadeSpeed, Text = check.Description, Font = "robotocondensed-regular.ttf", FontSize = 16, Color = "1 1 1 1", Align = TextAnchor.MiddleCenter
}
}
, check.IPAndPort);
}
if (startPosition < 0.5f) {
startPosition = 0.75f;
}
else if (startPosition > 0.5f) {
startPosition = 0.25f;
topPosition -= panelWidth + 15;
}
else {
topPosition -= panelWidth + 15;
}
}
CuiHelper.AddUi(player, container);
}
#endregion
}
}