C#:
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("LiveCommits", "MediocratyItself", "1.0.0")]
[Description("View Facepunch commits history in real-time")]
class LiveCommits : RustPlugin
{
#region Configuration
private Configuration config;
public class Configuration
{
[JsonProperty("Show button")]
public bool ShowButton { get; set; } = true;
[JsonProperty("Refresh rate (seconds)")]
public int RefreshRate { get; set; } = 10;
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<Configuration>();
if (config == null)
{
throw new JsonException();
}
}
catch
{
PrintWarning("Creating new configuration file");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void LoadDefaultConfig()
{
config = new Configuration();
}
protected override void SaveConfig()
{
Config.WriteObject(config, true);
}
#endregion
#region Fields
private Dictionary<ulong, int> playerPages = new Dictionary<ulong, int>();
private Dictionary<ulong, int> playerScrollPos = new Dictionary<ulong, int>();
private Dictionary<ulong, int> playerViewingCommit = new Dictionary<ulong, int>();
private Dictionary<ulong, string> playerViewingDev = new Dictionary<ulong, string>();
private Dictionary<ulong, int> playerDevPages = new Dictionary<ulong, int>();
private Dictionary<ulong, string> playerFilterMode = new Dictionary<ulong, string>();
private HashSet<ulong> playersWithUIOpen = new HashSet<ulong>();
private Dictionary<int, CommitData> pageCache = new Dictionary<int, CommitData>();
private Dictionary<string, Dictionary<int, CommitData>> devPageCache = new Dictionary<string, Dictionary<int, CommitData>>();
private DateTime lastCacheUpdate = DateTime.MinValue;
private const int CacheExpiryMinutes = 5;
private int totalCommits = 0;
private Timer refreshTimer;
private Dictionary<string, Timer> uiTimers = new Dictionary<string, Timer>();
private int lastCommitId = 0;
private SteamBuildInfo cachedSteamBuild;
private DateTime lastSteamBuildCheck = DateTime.MinValue;
private const int SteamBuildCacheMinutes = 5;
#endregion
#region Data Classes
public class CommitData
{
public int total { get; set; }
public int skip { get; set; }
public int take { get; set; }
public List<CommitResult> results { get; set; }
}
public class CommitResult
{
public int id { get; set; }
public string repo { get; set; }
public string branch { get; set; }
public string changeset { get; set; }
public DateTime created { get; set; }
public int likes { get; set; }
public int dislikes { get; set; }
public string message { get; set; }
public CommitUser user { get; set; }
}
public class CommitUser
{
public string name { get; set; }
public string avatar { get; set; }
public string id { get; set; }
public string login { get; set; }
public string username { get; set; }
}
public class SteamBuildInfo
{
public string Branch { get; set; }
public string BuildId { get; set; }
public string TimeUpdated { get; set; }
public DateTime LastChecked { get; set; }
}
#endregion
#region Oxide Hooks
void OnServerInitialized()
{
if (config.ShowButton)
{
foreach (var player in BasePlayer.activePlayerList)
{
CreateToggleButton(player);
}
}
FetchCommitData(null, 1);
if (config.RefreshRate > 0)
{
refreshTimer = timer.Every(config.RefreshRate, () => CheckForNewCommits());
}
}
void Unload()
{
foreach (var player in BasePlayer.activePlayerList)
{
DestroyAllUI(player);
}
refreshTimer?.Destroy();
foreach (var uiTimer in uiTimers.Values)
{
uiTimer?.Destroy();
}
uiTimers.Clear();
}
#endregion
#region Commands
[ChatCommand("commits")]
void CommitsCommand(BasePlayer player, string command, string[] args)
{
if (!playerPages.ContainsKey(player.userID))
playerPages[player.userID] = 1;
if (!playerScrollPos.ContainsKey(player.userID))
playerScrollPos[player.userID] = 0;
if (!playerFilterMode.ContainsKey(player.userID))
playerFilterMode[player.userID] = "all";
playerViewingCommit.Remove(player.userID);
playerViewingDev.Remove(player.userID);
ShowCommitUI(player);
}
[ConsoleCommand("livecommits.close")]
void CloseUICommand(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
DestroyUI(player);
playersWithUIOpen.Remove(player.userID);
playerViewingCommit.Remove(player.userID);
playerViewingDev.Remove(player.userID);
StopUIUpdateTimer(player);
}
[ConsoleCommand("livecommits.page")]
void PageCommand(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
int page = arg.GetInt(0, 1);
if (page < 1) page = 1;
int totalPages = (int)Math.Ceiling(totalCommits / 100.0);
if (page > totalPages) page = totalPages;
playerPages[player.userID] = page;
playerScrollPos[player.userID] = 0;
playerViewingCommit.Remove(player.userID);
playerViewingDev.Remove(player.userID);
FetchCommitData(player, page);
}
[ConsoleCommand("livecommits.goto")]
void GotoPageCommand(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
string input = arg.GetString(0, "1");
int page;
if (!int.TryParse(input, out page)) page = 1;
if (page < 1) page = 1;
int totalPages = (int)Math.Ceiling(totalCommits / 100.0);
if (page > totalPages) page = totalPages;
playerPages[player.userID] = page;
playerScrollPos[player.userID] = 0;
playerViewingCommit.Remove(player.userID);
playerViewingDev.Remove(player.userID);
FetchCommitData(player, page);
}
[ConsoleCommand("livecommits.view")]
void ViewCommitCommand(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
int commitIndex = arg.GetInt(0, -1);
if (commitIndex < 0) return;
int currentPage = playerPages.ContainsKey(player.userID) ? playerPages[player.userID] : 1;
if (playerViewingDev.ContainsKey(player.userID))
{
string devKey = playerViewingDev[player.userID];
int devPage = playerDevPages.ContainsKey(player.userID) ? playerDevPages[player.userID] : 1;
if (devPageCache.ContainsKey(devKey) && devPageCache[devKey].ContainsKey(devPage))
{
var devData = devPageCache[devKey][devPage];
if (commitIndex < devData.results.Count)
{
playerViewingCommit[player.userID] = commitIndex;
DrawCommitDetailUI(player, devData.results[commitIndex], devPage, true, commitIndex);
}
}
return;
}
if (!pageCache.ContainsKey(currentPage)) return;
if (commitIndex >= pageCache[currentPage].results.Count) return;
playerViewingCommit[player.userID] = commitIndex;
DrawCommitDetailUI(player, pageCache[currentPage].results[commitIndex], currentPage, false, commitIndex);
}
[ConsoleCommand("livecommits.back")]
void BackCommand(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
CuiHelper.DestroyUi(player, "LiveCommitsDetailOverlay");
playerViewingCommit.Remove(player.userID);
}
[ConsoleCommand("livecommits.backtoall")]
void BackToAllCommand(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
playerViewingDev.Remove(player.userID);
playerViewingCommit.Remove(player.userID);
int currentPage = playerPages.ContainsKey(player.userID) ? playerPages[player.userID] : 1;
if (pageCache.ContainsKey(currentPage))
{
DrawCommitUI(player, pageCache[currentPage], currentPage);
}
else
{
FetchCommitData(player, currentPage);
}
}
[ConsoleCommand("livecommits.viewdev")]
void ViewDevCommand(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
int commitIndex = arg.GetInt(0, -1);
if (commitIndex < 0) return;
int currentPage = playerPages.ContainsKey(player.userID) ? playerPages[player.userID] : 1;
CommitData data = null;
if (playerViewingDev.ContainsKey(player.userID))
{
string devKey = playerViewingDev[player.userID];
int devPage = playerDevPages.ContainsKey(player.userID) ? playerDevPages[player.userID] : 1;
if (devPageCache.ContainsKey(devKey) && devPageCache[devKey].ContainsKey(devPage))
{
data = devPageCache[devKey][devPage];
}
}
else if (pageCache.ContainsKey(currentPage))
{
data = pageCache[currentPage];
}
if (data == null || commitIndex >= data.results.Count) return;
var commit = data.results[commitIndex];
string devName = commit.user?.name ?? "Unknown";
string repo = commit.repo ?? "rust_reboot";
playerViewingDev[player.userID] = $"{devName}/{repo}";
playerDevPages[player.userID] = 1;
playerViewingCommit.Remove(player.userID);
FetchDevCommitData(player, devName, repo, 1);
}
[ConsoleCommand("livecommits.devpage")]
void DevPageCommand(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
if (!playerViewingDev.ContainsKey(player.userID)) return;
int page = arg.GetInt(0, 1);
if (page < 1) page = 1;
string devKey = playerViewingDev[player.userID];
string[] parts = devKey.Split('/');
if (parts.Length != 2) return;
playerDevPages[player.userID] = page;
playerViewingCommit.Remove(player.userID);
FetchDevCommitData(player, parts[0], parts[1], page);
}
[ConsoleCommand("livecommits.toggle")]
void ToggleCommand(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
if (playersWithUIOpen.Contains(player.userID))
{
DestroyUI(player);
playersWithUIOpen.Remove(player.userID);
playerViewingCommit.Remove(player.userID);
playerViewingDev.Remove(player.userID);
StopUIUpdateTimer(player);
return;
}
if (!playerPages.ContainsKey(player.userID))
playerPages[player.userID] = 1;
if (!playerScrollPos.ContainsKey(player.userID))
playerScrollPos[player.userID] = 0;
if (!playerFilterMode.ContainsKey(player.userID))
playerFilterMode[player.userID] = "all";
playerViewingCommit.Remove(player.userID);
playerViewingDev.Remove(player.userID);
ShowCommitUI(player);
}
[ConsoleCommand("livecommits.filter")]
void FilterCommand(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
string mode = arg.GetString(0, "all");
playerFilterMode[player.userID] = mode;
playerPages[player.userID] = 1;
playerViewingCommit.Remove(player.userID);
playerViewingDev.Remove(player.userID);
if (mode == "updates")
{
ShowUpdatesView(player);
}
else
{
int currentPage = 1;
if (pageCache.ContainsKey(currentPage))
{
DrawCommitUI(player, pageCache[currentPage], currentPage);
}
else
{
FetchCommitData(player, currentPage);
}
}
}
#endregion
#region UI Creation
void CreateToggleButton(BasePlayer player)
{
CuiHelper.DestroyUi(player, "LiveCommitsToggleBtn");
var container = new CuiElementContainer();
container.Add(new CuiPanel
{
Image = { Color = "0.12 0.12 0.12 0.97" },
RectTransform = { AnchorMin = "0.885 0.94", AnchorMax = "0.998 0.998" }
}, "Overlay", "LiveCommitsToggleBtn");
container.Add(new CuiElement
{
Parent = "LiveCommitsToggleBtn",
Components = {
new CuiRawImageComponent { Url = "https://www.playrust.nl/wp-content/uploads/2017/02/Rust-Logo.jpg", Color = "1 1 1 1" },
new CuiRectTransformComponent { AnchorMin = "0.03 0.12", AnchorMax = "0.22 0.88" }
}
});
container.Add(new CuiLabel
{
Text = { Text = "FACEPUNCH", FontSize = 12, Align = TextAnchor.MiddleLeft, Color = "1 1 1 1", Font = "robotocondensed-bold.ttf" },
RectTransform = { AnchorMin = "0.25 0.52", AnchorMax = "0.97 0.95" }
}, "LiveCommitsToggleBtn");
container.Add(new CuiLabel
{
Text = { Text = "COMMITS", FontSize = 11, Align = TextAnchor.MiddleLeft, Color = "0.55 0.8 0.35 1", Font = "robotocondensed-bold.ttf" },
RectTransform = { AnchorMin = "0.25 0.08", AnchorMax = "0.97 0.52" }
}, "LiveCommitsToggleBtn");
container.Add(new CuiButton
{
Button = { Command = "livecommits.toggle", Color = "0 0 0 0" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
Text = { Text = "" }
}, "LiveCommitsToggleBtn");
CuiHelper.AddUi(player, container);
}
void ShowCommitUI(BasePlayer player)
{
DestroyUI(player);
int currentPage = playerPages.ContainsKey(player.userID) ? playerPages[player.userID] : 1;
if (pageCache.ContainsKey(currentPage) && (DateTime.Now - lastCacheUpdate).TotalMinutes < CacheExpiryMinutes)
{
DrawCommitUI(player, pageCache[currentPage], currentPage);
return;
}
FetchCommitData(player, currentPage);
}
void DrawCommitUI(BasePlayer player, CommitData data, int currentPage)
{
CuiHelper.DestroyUi(player, "LiveCommitsOverlay");
CuiHelper.DestroyUi(player, "LiveCommitsDetailOverlay");
CuiHelper.DestroyUi(player, "HeaderAvatar");
CuiHelper.DestroyUi(player, "LiveCommitsHeader");
CuiHelper.DestroyUi(player, "LiveCommitsMain");
var container = new CuiElementContainer();
container.Add(new CuiPanel
{
Image = { Color = "0 0 0 0.9" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
CursorEnabled = true
}, "Overlay", "LiveCommitsOverlay");
container.Add(new CuiButton
{
Button = { Command = "livecommits.close", Color = "0 0 0 0" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
Text = { Text = "" }
}, "LiveCommitsOverlay", "LiveCommitsBgClose");
container.Add(new CuiPanel
{
Image = { Color = "0.1 0.1 0.1 0.99" },
RectTransform = { AnchorMin = "0.1 0.06", AnchorMax = "0.9 0.92" }
}, "LiveCommitsOverlay", "LiveCommitsMain");
container.Add(new CuiButton
{
Button = { Command = "", Color = "0 0 0 0" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
Text = { Text = "" }
}, "LiveCommitsMain");
container.Add(new CuiPanel
{
Image = { Color = "0.4 0.7 0.3 1" },
RectTransform = { AnchorMin = "0 0.9975", AnchorMax = "1 1" }
}, "LiveCommitsMain");
container.Add(new CuiPanel
{
Image = { Color = "0.13 0.13 0.13 1" },
RectTransform = { AnchorMin = "0 0.935", AnchorMax = "1 0.9975" }
}, "LiveCommitsMain", "LiveCommitsHeader");
bool isDevView = playerViewingDev.ContainsKey(player.userID);
if (isDevView && data.results.Count > 0 && !string.IsNullOrEmpty(data.results[0].user?.avatar))
{
string avatarUrl = data.results[0].user.avatar;
if (!avatarUrl.Contains("?"))
avatarUrl += $"?t={DateTime.Now.Ticks}";
else
avatarUrl += $"&t={DateTime.Now.Ticks}";
container.Add(new CuiElement
{
Parent = "LiveCommitsHeader",
Name = "HeaderAvatar",
Components = {
new CuiRawImageComponent { Url = avatarUrl, Color = "1 1 1 1" },
new CuiRectTransformComponent { AnchorMin = "0.01 0.1", AnchorMax = "0.04 0.9" }
}
});
}
else
{
container.Add(new CuiElement
{
Parent = "LiveCommitsHeader",
Name = "HeaderAvatar",
Components = {
new CuiRawImageComponent { Url = "https://pbs.twimg.com/profile_images/1247149194093133825/N_9ul0cD_400x400.png", Color = "1 1 1 1" },
new CuiRectTransformComponent { AnchorMin = "0.01 0.1", AnchorMax = "0.04 0.9" }
}
});
}
if (isDevView)
{
string devName = playerViewingDev[player.userID].Split('/')[0].ToUpper();
container.Add(new CuiLabel
{
Text = { Text = devName, FontSize = 17, Align = TextAnchor.MiddleLeft, Color = "1 1 1 1", Font = "robotocondensed-bold.ttf" },
RectTransform = { AnchorMin = "0.05 0.5", AnchorMax = "0.4 1" }
}, "LiveCommitsHeader");
container.Add(new CuiLabel
{
Text = { Text = "COMMITS", FontSize = 15, Align = TextAnchor.MiddleLeft, Color = "0.55 0.8 0.35 1", Font = "robotocondensed-bold.ttf" },
RectTransform = { AnchorMin = "0.05 0", AnchorMax = "0.4 0.5" }
}, "LiveCommitsHeader");
}
else
{
container.Add(new CuiLabel
{
Text = { Text = "FACEPUNCH RUST COMMITS", FontSize = 17, Align = TextAnchor.MiddleLeft, Color = "1 1 1 1", Font = "robotocondensed-bold.ttf" },
RectTransform = { AnchorMin = "0.05 0", AnchorMax = "0.4 1" }
}, "LiveCommitsHeader");
}
if (isDevView)
{
container.Add(new CuiButton
{
Button = { Command = "livecommits.backtoall", Color = "0.25 0.25 0.25 1" },
RectTransform = { AnchorMin = "0.41 0.15", AnchorMax = "0.52 0.85" },
Text = { Text = "◀ All Commits", FontSize = 11, Align = TextAnchor.MiddleCenter, Color = "0.9 0.9 0.9 1", Font = "robotocondensed-regular.ttf" }
}, "LiveCommitsHeader");
}
int totalDays = (int)(DateTime.Now - new DateTime(2013, 1, 1)).TotalDays;
double cph = data.total / (double)totalDays / 24.0;
container.Add(new CuiLabel
{
Text = { Text = $"<color=#8BC34A>{data.total:N0}</color> Commits", FontSize = 12, Align = TextAnchor.MiddleRight, Color = "0.75 0.75 0.75 1", Font = "robotocondensed-regular.ttf" },
RectTransform = { AnchorMin = "0.53 0", AnchorMax = "0.92 1" }
}, "LiveCommitsHeader");
container.Add(new CuiButton
{
Button = { Command = "livecommits.close", Color = "0.75 0.22 0.18 1" },
RectTransform = { AnchorMin = "0.955 0.15", AnchorMax = "0.99 0.85" },
Text = { Text = "✕", FontSize = 18, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1", Font = "robotocondensed-bold.ttf" }
}, "LiveCommitsHeader");
if (!isDevView)
{
DrawFilterSidebar(container, player);
}
string contentAnchorMin = isDevView ? "0.008 0.075" : "0.128 0.075";
string filterMode = playerFilterMode.ContainsKey(player.userID) ? playerFilterMode[player.userID] : "all";
List<CommitResult> filteredCommits = FilterCommits(data.results, filterMode);
if (!isDevView && (filterMode == "merges" || filterMode == "undo"))
{
container.Add(new CuiPanel
{
Image = { Color = "0.15 0.15 0.15 1" },
RectTransform = { AnchorMin = contentAnchorMin, AnchorMax = "0.992 0.105" }
}, "LiveCommitsMain", "TimeRangeBar");
if (filteredCommits.Count > 0)
{
DateTime earliest = filteredCommits.Max(c => c.created);
DateTime latest = filteredCommits.Min(c => c.created);
string timeRangeText = $"Time Range: {earliest:MM/dd/yyyy HH:mm} - {latest:MM/dd/yyyy HH:mm}";
container.Add(new CuiLabel
{
Text = { Text = timeRangeText, FontSize = 11, Align = TextAnchor.MiddleCenter, Color = "0.7 0.7 0.7 1", Font = "robotocondensed-regular.ttf" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
}, "TimeRangeBar");
}
else
{
string filterName = filterMode == "merges" ? "Merges" : "Undos";
container.Add(new CuiLabel
{
Text = { Text = $"No {filterName} on this page", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = "0.9 0.5 0.3 1", Font = "robotocondensed-bold.ttf" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
}, "TimeRangeBar");
}
contentAnchorMin = isDevView ? "0.008 0.075" : "0.128 0.11";
}
container.Add(new CuiPanel
{
Image = { Color = "0.08 0.08 0.08 1" },
RectTransform = { AnchorMin = contentAnchorMin, AnchorMax = "0.992 0.93" }
}, "LiveCommitsMain", "LiveCommitsContent");
Dictionary<int, int> filterIndexMapping = new Dictionary<int, int>();
if (filterMode != "all")
{
int filteredIndex = 0;
for (int i = 0; i < data.results.Count; i++)
{
if (filteredCommits.Contains(data.results[i]))
{
filterIndexMapping[filteredIndex] = i;
filteredIndex++;
}
}
}
container.Add(new CuiElement
{
Parent = "LiveCommitsContent",
Name = "LiveCommitsScrollView",
Components = {
new CuiScrollViewComponent {
Vertical = true,
Horizontal = false,
MovementType = UnityEngine.UI.ScrollRect.MovementType.Clamped,
Elasticity = 0.1f,
Inertia = true,
DecelerationRate = 0.2f,
ScrollSensitivity = 30f,
ContentTransform = new CuiRectTransform {
AnchorMin = "0 1",
AnchorMax = "1 1",
OffsetMin = $"0 -{filteredCommits.Count * 65}",
OffsetMax = "0 0"
},
VerticalScrollbar = new CuiScrollbar {
AutoHide = false,
HandleColor = "0.4 0.6 0.3 0.8",
HighlightColor = "0.5 0.7 0.4 1",
TrackColor = "0.15 0.15 0.15 1",
Size = 12
}
},
new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1" }
}
});
float itemHeight = 65f;
float yOffset = 0;
for (int i = 0; i < filteredCommits.Count; i++)
{
var commit = filteredCommits[i];
int originalIndex = filterMode != "all" ? filterIndexMapping[i] : i;
DrawCommitItemScrollable(container, "LiveCommitsScrollView", commit, yOffset, itemHeight, originalIndex, isDevView);
yOffset += itemHeight;
}
DrawPagination(container, data, currentPage, isDevView);
DrawDisclaimer(container);
CuiHelper.AddUi(player, container);
playersWithUIOpen.Add(player.userID);
StartUIUpdateTimer(player);
}
void DrawFilterSidebar(CuiElementContainer container, BasePlayer player)
{
container.Add(new CuiPanel
{
Image = { Color = "0.12 0.12 0.12 1" },
RectTransform = { AnchorMin = "0.008 0.075", AnchorMax = "0.118 0.93" }
}, "LiveCommitsMain", "FilterSidebar");
container.Add(new CuiLabel
{
Text = { Text = "FILTER", FontSize = 13, Align = TextAnchor.MiddleCenter, Color = "0.55 0.8 0.35 1", Font = "robotocondensed-bold.ttf" },
RectTransform = { AnchorMin = "0 0.95", AnchorMax = "1 0.99" }
}, "FilterSidebar");
container.Add(new CuiPanel
{
Image = { Color = "0.4 0.7 0.3 1" },
RectTransform = { AnchorMin = "0.05 0.945", AnchorMax = "0.95 0.948" }
}, "FilterSidebar");
string currentFilter = playerFilterMode.ContainsKey(player.userID) ? playerFilterMode[player.userID] : "all";
string allColor = currentFilter == "all" ? "0.4 0.7 0.3 1" : "0.18 0.18 0.18 1";
string allTextColor = currentFilter == "all" ? "1 1 1 1" : "0.7 0.7 0.7 1";
container.Add(new CuiButton
{
Button = { Command = "livecommits.filter all", Color = allColor },
RectTransform = { AnchorMin = "0.08 0.87", AnchorMax = "0.92 0.93" },
Text = { Text = "All Commits", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = allTextColor, Font = "robotocondensed-bold.ttf" }
}, "FilterSidebar");
string mergesColor = currentFilter == "merges" ? "0.4 0.7 0.3 1" : "0.18 0.18 0.18 1";
string mergesTextColor = currentFilter == "merges" ? "1 1 1 1" : "0.7 0.7 0.7 1";
container.Add(new CuiButton
{
Button = { Command = "livecommits.filter merges", Color = mergesColor },
RectTransform = { AnchorMin = "0.08 0.8", AnchorMax = "0.92 0.86" },
Text = { Text = "Merges", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = mergesTextColor, Font = "robotocondensed-bold.ttf" }
}, "FilterSidebar");
string undoColor = currentFilter == "undo" ? "0.4 0.7 0.3 1" : "0.18 0.18 0.18 1";
string undoTextColor = currentFilter == "undo" ? "1 1 1 1" : "0.7 0.7 0.7 1";
container.Add(new CuiButton
{
Button = { Command = "livecommits.filter undo", Color = undoColor },
RectTransform = { AnchorMin = "0.08 0.73", AnchorMax = "0.92 0.79" },
Text = { Text = "Undo", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = undoTextColor, Font = "robotocondensed-bold.ttf" }
}, "FilterSidebar");
string updatesColor = currentFilter == "updates" ? "0.4 0.7 0.3 1" : "0.18 0.18 0.18 1";
string updatesTextColor = currentFilter == "updates" ? "1 1 1 1" : "0.7 0.7 0.7 1";
container.Add(new CuiButton
{
Button = { Command = "livecommits.filter updates", Color = updatesColor },
RectTransform = { AnchorMin = "0.08 0.66", AnchorMax = "0.92 0.72" },
Text = { Text = "Updates", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = updatesTextColor, Font = "robotocondensed-bold.ttf" }
}, "FilterSidebar");
}
List<CommitResult> FilterCommits(List<CommitResult> commits, string filterMode)
{
if (filterMode == "all")
{
return commits;
}
else if (filterMode == "merges")
{
return commits.Where(c =>
!string.IsNullOrEmpty(c.message) &&
c.message.ToLower().Contains("merge")
).ToList();
}
else if (filterMode == "undo")
{
return commits.Where(c =>
!string.IsNullOrEmpty(c.message) &&
c.message.ToLower().Contains("undo")
).ToList();
}
else if (filterMode == "updates")
{
return new List<CommitResult>();
}
return commits;
}
void DrawCommitDetailUI(BasePlayer player, CommitResult commit, int returnPage, bool isDevView, int commitIndex)
{
CuiHelper.DestroyUi(player, "LiveCommitsDetailOverlay");
var container = new CuiElementContainer();
container.Add(new CuiPanel
{
Image = { Color = "0 0 0 0.85", Material = "assets/content/ui/uibackgroundblur.mat" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
CursorEnabled = true
}, "Overlay", "LiveCommitsDetailOverlay");
container.Add(new CuiButton
{
Button = { Command = "livecommits.back", Color = "0 0 0 0" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
Text = { Text = "" }
}, "LiveCommitsDetailOverlay", "DetailBgClose");
container.Add(new CuiPanel
{
Image = { Color = "0.1 0.1 0.1 0.99" },
RectTransform = { AnchorMin = "0.2 0.15", AnchorMax = "0.8 0.85" }
}, "LiveCommitsDetailOverlay", "LiveCommitsDetail");
container.Add(new CuiButton
{
Button = { Command = "", Color = "0 0 0 0" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
Text = { Text = "" }
}, "LiveCommitsDetail");
container.Add(new CuiPanel
{
Image = { Color = "0.4 0.7 0.3 1" },
RectTransform = { AnchorMin = "0 0.995", AnchorMax = "1 1" }
}, "LiveCommitsDetail");
container.Add(new CuiPanel
{
Image = { Color = "0.13 0.13 0.13 1" },
RectTransform = { AnchorMin = "0 0.92", AnchorMax = "1 0.995" }
}, "LiveCommitsDetail", "DetailHeader");
container.Add(new CuiButton
{
Button = { Command = "livecommits.back", Color = "0.25 0.25 0.25 1" },
RectTransform = { AnchorMin = "0.01 0.15", AnchorMax = "0.08 0.85" },
Text = { Text = "◀ Back", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = "0.9 0.9 0.9 1", Font = "robotocondensed-regular.ttf" }
}, "DetailHeader");
container.Add(new CuiLabel
{
Text = { Text = "COMMIT DETAILS", FontSize = 16, Align = TextAnchor.MiddleLeft, Color = "1 1 1 1", Font = "robotocondensed-bold.ttf" },
RectTransform = { AnchorMin = "0.1 0", AnchorMax = "0.5 1" }
}, "DetailHeader");
container.Add(new CuiLabel
{
Text = { Text = $"#{commit.changeset}", FontSize = 14, Align = TextAnchor.MiddleRight, Color = "0.4 0.6 0.85 1", Font = "robotocondensed-bold.ttf" },
RectTransform = { AnchorMin = "0.5 0", AnchorMax = "0.92 1" }
}, "DetailHeader");
container.Add(new CuiButton
{
Button = { Command = "livecommits.back", Color = "0.75 0.22 0.18 1" },
RectTransform = { AnchorMin = "0.94 0.15", AnchorMax = "0.99 0.85" },
Text = { Text = "✕", FontSize = 16, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1", Font = "robotocondensed-bold.ttf" }
}, "DetailHeader");
container.Add(new CuiPanel
{
Image = { Color = "0.08 0.08 0.08 1" },
RectTransform = { AnchorMin = "0.015 0.04", AnchorMax = "0.985 0.91" }
}, "LiveCommitsDetail", "DetailContent");
if (!string.IsNullOrEmpty(commit.user?.avatar))
{
container.Add(new CuiElement
{
Parent = "DetailContent",
Name = "DetailAvatar",
Components = {
new CuiRawImageComponent { Url = commit.user.avatar, Color = "1 1 1 1" },
new CuiRectTransformComponent { AnchorMin = "0.03 0.7", AnchorMax = "0.15 0.95" }
}
});
container.Add(new CuiButton
{
Button = { Command = $"livecommits.viewdev {commitIndex}", Color = "0 0 0 0" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
Text = { Text = "" }
}, "DetailAvatar");
}
string userName = commit.user?.name ?? "Unknown";
container.Add(new CuiPanel
{
Image = { Color = "0 0 0 0" },
RectTransform = { AnchorMin = "0.17 0.82", AnchorMax = "0.7 0.95" }
}, "DetailContent", "UserNamePanel");
container.Add(new CuiLabel
{
Text = { Text = userName, FontSize = 22, Align = TextAnchor.MiddleLeft, Color = "0.3 0.55 0.2 1", Font = "robotocondensed-bold.ttf" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
}, "UserNamePanel");
container.Add(new CuiButton
{
Button = { Command = $"livecommits.viewdev {commitIndex}", Color = "0 0 0 0" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
Text = { Text = "" }
}, "UserNamePanel");
string exactTime = commit.created.ToString("dddd, MMMM dd, yyyy 'at' HH:mm:ss 'UTC'");
container.Add(new CuiLabel
{
Text = { Text = exactTime, FontSize = 13, Align = TextAnchor.MiddleLeft, Color = "0.6 0.6 0.6 1", Font = "robotocondensed-regular.ttf" },
RectTransform = { AnchorMin = "0.17 0.74", AnchorMax = "0.7 0.82" }
}, "DetailContent");
string timeAgo = GetTimeAgoDetailed(commit.created);
container.Add(new CuiLabel
{
Text = { Text = $"({timeAgo})", FontSize = 12, Align = TextAnchor.MiddleLeft, Color = "0.5 0.5 0.5 1", Font = "robotocondensed-regular.ttf" },
RectTransform = { AnchorMin = "0.17 0.67", AnchorMax = "0.7 0.74" }
}, "DetailContent");
container.Add(new CuiLabel
{
Text = { Text = $"<color=#6B9E3A>▲</color> {commit.likes} <color=#C75050>▼</color> {commit.dislikes}", FontSize = 16, Align = TextAnchor.MiddleRight, Color = "0.7 0.7 0.7 1", Font = "robotocondensed-regular.ttf" },
RectTransform = { AnchorMin = "0.7 0.82", AnchorMax = "0.97 0.95" }
}, "DetailContent");
container.Add(new CuiPanel
{
Image = { Color = "0.2 0.2 0.2 1" },
RectTransform = { AnchorMin = "0.03 0.64", AnchorMax = "0.97 0.645" }
}, "DetailContent");
string repoName = commit.repo ?? "";
string branchName = commit.branch ?? "";
container.Add(new CuiLabel
{
Text = { Text = $"<color=#5A9E3A>{repoName}/</color><color=#D4883A>{branchName}</color>", FontSize = 14, Align = TextAnchor.MiddleLeft, Color = "1 1 1 1", Font = "robotocondensed-regular.ttf" },
RectTransform = { AnchorMin = "0.03 0.56", AnchorMax = "0.97 0.63" }
}, "DetailContent");
container.Add(new CuiLabel
{
Text = { Text = "Commit Message:", FontSize = 11, Align = TextAnchor.MiddleLeft, Color = "0.5 0.5 0.5 1", Font = "robotocondensed-regular.ttf" },
RectTransform = { AnchorMin = "0.03 0.48", AnchorMax = "0.97 0.54" }
}, "DetailContent");
string message = FormatCommitMessage(commit.message ?? "");
container.Add(new CuiPanel
{
Image = { Color = "0.08 0.08 0.08 1" },
RectTransform = { AnchorMin = "0.03 0.08", AnchorMax = "0.97 0.47" }
}, "DetailContent", "MessageBox");
container.Add(new CuiLabel
{
Text = { Text = message, FontSize = 13, Align = TextAnchor.UpperLeft, Color = "0.75 0.75 0.75 1", Font = "robotocondensed-regular.ttf" },
RectTransform = { AnchorMin = "0.02 0.02", AnchorMax = "0.98 0.98" }
}, "MessageBox");
container.Add(new CuiPanel
{
Image = { Color = "0.12 0.12 0.12 1" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 0.035" }
}, "LiveCommitsDetail", "DetailDisclaimer");
container.Add(new CuiLabel
{
Text = { Text = "This plugin is unofficial and not affiliated with or endorsed by Facepunch Studios.", FontSize = 9, Align = TextAnchor.MiddleCenter, Color = "0.7 0.4 0.4 1", Font = "robotocondensed-regular.ttf" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
}, "DetailDisclaimer");
CuiHelper.AddUi(player, container);
}
void DrawCommitItemScrollable(CuiElementContainer container, string parent, CommitResult commit, float yOffset, float height, int index, bool isDevView)
{
string panelName = $"CommitItem{index}";
string bgColor = index % 2 == 0 ? "0.09 0.09 0.09 1" : "0.07 0.07 0.07 1";
container.Add(new CuiPanel
{
Image = { Color = bgColor },
RectTransform = {
AnchorMin = "0 1",
AnchorMax = "1 1",
OffsetMin = $"0 -{yOffset + height}",
OffsetMax = $"0 -{yOffset}"
}
}, parent, panelName);
container.Add(new CuiPanel
{
Image = { Color = "0.35 0.6 0.25 1" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "0.004 1" }
}, panelName);
string userName = commit.user?.name ?? "Unknown";
string repoName = commit.repo ?? "";
if (isDevView)
{
string timeAgo = GetTimeAgoDetailed(commit.created);
container.Add(new CuiLabel
{
Text = { Text = timeAgo, FontSize = 12, Align = TextAnchor.MiddleLeft, Color = "0.6 0.6 0.6 1", Font = "robotocondensed-bold.ttf" },
RectTransform = { AnchorMin = "0.02 0.3", AnchorMax = "0.2 0.8" }
}, panelName, $"DevTimeLabel{index}");
}
else
{
if (!string.IsNullOrEmpty(commit.user?.avatar))
{
container.Add(new CuiElement
{
Parent = panelName,
Name = $"Avatar{index}",
Components = {
new CuiRawImageComponent { Url = commit.user.avatar, Color = "1 1 1 1" },
new CuiRectTransformComponent { AnchorMin = "0.012 0.15", AnchorMax = "0.052 0.85" }
}
});
}
container.Add(new CuiLabel
{
Text = { Text = userName, FontSize = 11, Align = TextAnchor.MiddleLeft, Color = "0.3 0.55 0.2 1", Font = "robotocondensed-bold.ttf" },
RectTransform = { AnchorMin = "0.06 0.6", AnchorMax = "0.18 0.95" }
}, panelName);
string timeAgo = GetTimeAgoDetailed(commit.created);
container.Add(new CuiLabel
{
Text = { Text = timeAgo, FontSize = 9, Align = TextAnchor.MiddleLeft, Color = "0.45 0.45 0.45 1", Font = "robotocondensed-regular.ttf" },
RectTransform = { AnchorMin = "0.06 0.3", AnchorMax = "0.18 0.6" }
}, panelName, $"TimeLabel{index}");
}
string branchName = commit.branch ?? "";
container.Add(new CuiLabel
{
Text = { Text = $"<color=#5A9E3A>{repoName}/</color><color=#D4883A>{branchName}</color>", FontSize = 12, Align = TextAnchor.MiddleLeft, Color = "1 1 1 1", Font = "robotocondensed-regular.ttf" },
RectTransform = { AnchorMin = "0.185 0.6", AnchorMax = "0.7 0.95" }
}, panelName);
container.Add(new CuiLabel
{
Text = { Text = $"#{commit.changeset}", FontSize = 10, Align = TextAnchor.MiddleLeft, Color = "0.4 0.6 0.85 1", Font = "robotocondensed-bold.ttf" },
RectTransform = { AnchorMin = "0.7 0.6", AnchorMax = "0.82 0.95" }
}, panelName);
string message = FormatCommitMessage(commit.message ?? "");
if (message.Length > 90) message = message.Substring(0, 90) + "...";
container.Add(new CuiLabel
{
Text = { Text = message, FontSize = 11, Align = TextAnchor.MiddleLeft, Color = "0.7 0.7 0.7 1", Font = "robotocondensed-regular.ttf" },
RectTransform = { AnchorMin = "0.185 0.08", AnchorMax = "0.82 0.55" }
}, panelName);
container.Add(new CuiLabel
{
Text = { Text = $"<color=#6B9E3A>▲</color> {commit.likes}", FontSize = 10, Align = TextAnchor.MiddleCenter, Color = "0.6 0.6 0.6 1", Font = "robotocondensed-regular.ttf" },
RectTransform = { AnchorMin = "0.85 0.5", AnchorMax = "0.92 0.92" }
}, panelName);
container.Add(new CuiLabel
{
Text = { Text = $"<color=#C75050>▼</color> {commit.dislikes}", FontSize = 10, Align = TextAnchor.MiddleCenter, Color = "0.6 0.6 0.6 1", Font = "robotocondensed-regular.ttf" },
RectTransform = { AnchorMin = "0.92 0.5", AnchorMax = "0.99 0.92" }
}, panelName);
container.Add(new CuiButton
{
Button = { Command = $"livecommits.view {index}", Color = "0 0 0 0" },
RectTransform = { AnchorMin = "0.19 0", AnchorMax = "1 1" },
Text = { Text = "" }
}, panelName);
if (!isDevView)
{
if (!string.IsNullOrEmpty(commit.user?.avatar))
{
container.Add(new CuiButton
{
Button = { Command = $"livecommits.viewdev {index}", Color = "0 0 0 0" },
RectTransform = { AnchorMin = "0.012 0.15", AnchorMax = "0.052 0.85" },
Text = { Text = "" }
}, panelName, $"AvatarBtn{index}");
// ... [Code preview truncated due to length. Full source in attached file] ...