C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Oxide.Core;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("NewsBroadcaster", "YourName", "3.6.0")]
[Description("Modern popup news broadcaster with sleek pop-up design and advanced text scaling")]
public class NewsBroadcaster : RustPlugin
{
private const string PanelName = "NewsBroadcasterUI";
private const string HistoryPanel = "NewsHistoryUI";
private const string BackdropName = "NewsBackdropUI";
private const string DataFile = "NewsBroadcaster_Data";
private ConfigData config;
private readonly List<Announcement> announcements = new List<Announcement>();
private readonly Dictionary<ulong, HashSet<string>> playerUIs = new Dictionary<ulong, HashSet<string>>();
private readonly Dictionary<ulong, Timer> autoCloseTimers = new Dictionary<ulong, Timer>();
#region Data
class Announcement
{
public string Title;
public string ImageUrl;
public string Text;
public string Date;
public string Author;
public AnnouncementType Type;
public int Priority; // 1=Low, 2=Normal, 3=High, 4=Critical
}
enum AnnouncementType
{
Info,
Warning,
Alert,
Update,
Event
}
#endregion
#region Enhanced Config
class ConfigData
{
public GeneralSettings General { get; set; } = new GeneralSettings();
public UISettings UI { get; set; } = new UISettings();
public TextScalingSettings TextScaling { get; set; } = new TextScalingSettings();
public SoundSettings Sounds { get; set; } = new SoundSettings();
public AnimationSettings Animations { get; set; } = new AnimationSettings();
public AdvancedSettings Advanced { get; set; } = new AdvancedSettings();
}
class GeneralSettings
{
public int AnnouncementsPerPage { get; set; } = 4;
public int MaxStoredAnnouncements { get; set; } = 100;
public bool EnableDebugMode { get; set; } = false;
public List<string> AllowedCommands { get; set; } = new List<string> { "news", "announcements", "updates" };
}
class UISettings
{
public PositionSettings Position { get; set; } = new PositionSettings();
public BrandingSettings Branding { get; set; } = new BrandingSettings();
public BehaviorSettings Behavior { get; set; } = new BehaviorSettings();
public ColorSettings Colors { get; set; } = new ColorSettings();
public LayoutSettings Layout { get; set; } = new LayoutSettings();
}
class TextScalingSettings
{
public float GlobalTextScale { get; set; } = 1.0f; // Global multiplier for all text
public FontSizeSettings FontSizes { get; set; } = new FontSizeSettings();
public ResponsiveSettings Responsive { get; set; } = new ResponsiveSettings();
public AccessibilitySettings Accessibility { get; set; } = new AccessibilitySettings();
}
class FontSizeSettings
{
public int BaseTitleSize { get; set; } = 18;
public int BaseHeaderSize { get; set; } = 14;
public int BaseParagraphSize { get; set; } = 12;
public int BaseSmallSize { get; set; } = 10;
public int BaseTinySize { get; set; } = 8;
// Font family preferences
public string PrimaryFont { get; set; } = "robotocondensed-bold.ttf";
public string SecondaryFont { get; set; } = "robotocondensed-regular.ttf";
public string MonospaceFont { get; set; } = "droidsansmono.ttf";
}
class ResponsiveSettings
{
public bool EnableResponsiveText { get; set; } = true;
public Dictionary<string, float> ScreenSizeMultipliers { get; set; } = new Dictionary<string, float>
{
["small"] = 0.8f, // For smaller UI scales
["normal"] = 1.0f, // Default
["large"] = 1.2f, // For better readability
["xlarge"] = 1.4f // For accessibility
};
public string DefaultScreenSize { get; set; } = "normal";
}
class AccessibilitySettings
{
public bool HighContrastMode { get; set; } = false;
public bool BoldText { get; set; } = false;
public float LineSpacing { get; set; } = 1.0f;
public int MaxTextWidth { get; set; } = 50; // Characters per line
public bool UseAccessibleColors { get; set; } = false;
}
class LayoutSettings
{
public PopupSizePresets PopupSize { get; set; } = PopupSizePresets.Medium;
public bool ShowImagePreviews { get; set; } = true;
public bool ShowTypeIcons { get; set; } = true;
public bool ShowPriorityIndicators { get; set; } = true;
public float ContentPadding { get; set; } = 0.02f;
public float ElementSpacing { get; set; } = 0.01f;
}
enum PopupSizePresets
{
Compact, // 0.35-0.65
Medium, // 0.3-0.7
Large, // 0.25-0.75
FullWidth // 0.1-0.9
}
class PositionSettings
{
public string Preset { get; set; } = "Center";
public string CustomAnchorMin { get; set; } = "0.3 0.3";
public string CustomAnchorMax { get; set; } = "0.7 0.7";
public Dictionary<string, PositionPreset> Presets { get; set; } = new Dictionary<string, PositionPreset>
{
["Center"] = new PositionPreset { AnchorMin = "0.3 0.3", AnchorMax = "0.7 0.7" },
["TopCenter"] = new PositionPreset { AnchorMin = "0.3 0.7", AnchorMax = "0.7 0.95" },
["BottomCenter"] = new PositionPreset { AnchorMin = "0.3 0.05", AnchorMax = "0.7 0.3" },
["LeftSide"] = new PositionPreset { AnchorMin = "0.05 0.3", AnchorMax = "0.45 0.7" },
["RightSide"] = new PositionPreset { AnchorMin = "0.55 0.3", AnchorMax = "0.95 0.7" },
["FullScreen"] = new PositionPreset { AnchorMin = "0.1 0.1", AnchorMax = "0.9 0.9" }
};
}
class PositionPreset
{
public string AnchorMin { get; set; }
public string AnchorMax { get; set; }
}
class BrandingSettings
{
public string LogoUrl { get; set; } = "";
public string HeaderImageUrl { get; set; } = "";
public string ServerName { get; set; } = "Server News";
public string WatermarkText { get; set; } = "";
public bool ShowServerLogo { get; set; } = true;
public LogoPosition LogoPosition { get; set; } = LogoPosition.HeaderLeft;
}
enum LogoPosition
{
HeaderLeft,
HeaderCenter,
HeaderRight,
BottomLeft,
BottomRight
}
class BehaviorSettings
{
public bool AutoCloseEnabled { get; set; } = true;
public int AutoCloseSeconds { get; set; } = 15;
public bool ShowCloseTimer { get; set; } = true;
public bool EnableSounds { get; set; } = true;
public bool EnableAnimations { get; set; } = true;
public bool BlockGameInput { get; set; } = false;
public bool ShowOnPlayerConnect { get; set; } = false;
public bool RememberPlayerPreferences { get; set; } = false;
}
class ColorSettings
{
// Base UI colors with better organization
public UIPanelColors Panels { get; set; } = new UIPanelColors();
public TextColors Text { get; set; } = new TextColors();
public ButtonColors Buttons { get; set; } = new ButtonColors();
public PriorityColors Priority { get; set; } = new PriorityColors();
public AccessibilityColors Accessibility { get; set; } = new AccessibilityColors();
}
class UIPanelColors
{
public string Backdrop { get; set; } = "0 0 0 0.6";
public string PopupPanel { get; set; } = "0.1 0.1 0.12 0.95";
public string PopupBorder { get; set; } = "0.95 0.5 0.2 0.8";
public string HeaderGradient { get; set; } = "0.2 0.2 0.25 0.9";
public string ContentPanel { get; set; } = "0.08 0.08 0.1 0.85";
public string HistoryPanel { get; set; } = "0.12 0.12 0.15 0.95";
public string CardPanel { get; set; } = "0.15 0.15 0.18 0.9";
public string AccentBar { get; set; } = "0.95 0.5 0.2 1";
}
class TextColors
{
public string TitleText { get; set; } = "1 1 1 1";
public string BodyText { get; set; } = "0.9 0.9 0.9 1";
public string MutedText { get; set; } = "0.7 0.7 0.7 1";
public string AccentText { get; set; } = "0.95 0.6 0.3 1";
public string ErrorText { get; set; } = "0.9 0.3 0.3 1";
public string SuccessText { get; set; } = "0.3 0.9 0.3 1";
public string WarningText { get; set; } = "0.9 0.9 0.3 1";
}
class ButtonColors
{
public string PrimaryButton { get; set; } = "0.25 0.6 0.9 0.9";
public string SecondaryButton { get; set; } = "0.5 0.5 0.5 0.8";
public string DangerButton { get; set; } = "0.8 0.3 0.3 0.9";
public string CloseButton { get; set; } = "0.9 0.4 0.4 0.95";
public string ButtonText { get; set; } = "1 1 1 1";
public string ButtonHover { get; set; } = "0.3 0.7 1 0.95";
}
class PriorityColors
{
public string InfoColor { get; set; } = "0.2 0.7 0.9 1";
public string WarningColor { get; set; } = "0.9 0.7 0.2 1";
public string AlertColor { get; set; } = "0.9 0.3 0.2 1";
public string EventColor { get; set; } = "0.6 0.3 0.9 1";
public string UpdateColor { get; set; } = "0.3 0.9 0.6 1";
}
class AccessibilityColors
{
public bool UseHighContrast { get; set; } = false;
public string HighContrastBackground { get; set; } = "0 0 0 1";
public string HighContrastText { get; set; } = "1 1 1 1";
public string HighContrastAccent { get; set; } = "1 1 0 1";
}
class SoundSettings
{
public string PopupSound { get; set; } = "assets/bundled/prefabs/fx/notice/item.select.fx.prefab";
public string CloseSound { get; set; } = "assets/bundled/prefabs/fx/notice/item.deselect.fx.prefab";
public string AlertSound { get; set; } = "assets/bundled/prefabs/fx/explosions/explosion_01.prefab";
public string WarningSound { get; set; } = "assets/bundled/prefabs/fx/notice/item.notice.fx.prefab";
public float SoundVolume { get; set; } = 1.0f;
public bool EnableSpatialAudio { get; set; } = false;
}
class AnimationSettings
{
public float SlideInDuration { get; set; } = 0.3f;
public float FadeInDuration { get; set; } = 0.2f;
public float SlideOutDuration { get; set; } = 0.2f;
public string AnimationType { get; set; } = "SlideFromTop"; // SlideFromTop, SlideFromBottom, FadeIn, ScaleIn
public bool EnableTransitions { get; set; } = true;
}
class AdvancedSettings
{
public int UIUpdateRate { get; set; } = 30; // Updates per second for animations
public bool UseAdvancedRendering { get; set; } = false;
public int MaxConcurrentPopups { get; set; } = 3;
public bool EnablePerformanceMode { get; set; } = false;
public Dictionary<string, object> CustomSettings { get; set; } = new Dictionary<string, object>();
}
#endregion
#region Config I/O
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<ConfigData>();
if (config == null) throw new Exception("Null config");
EnsureConfigDefaults();
}
catch
{
PrintError("[NewsBroadcaster] Invalid config.Creating defaults.");
config = new ConfigData();
}
SaveConfig();
}
private void EnsureConfigDefaults()
{
// Ensure all nested objects are initialized
if (config.General == null) config.General = new GeneralSettings();
if (config.UI == null) config.UI = new UISettings();
if (config.TextScaling == null) config.TextScaling = new TextScalingSettings();
if (config.Sounds == null) config.Sounds = new SoundSettings();
if (config.Animations == null) config.Animations = new AnimationSettings();
if (config.Advanced == null) config.Advanced = new AdvancedSettings();
// UI sub-objects
if (config.UI.Position == null) config.UI.Position = new PositionSettings();
if (config.UI.Branding == null) config.UI.Branding = new BrandingSettings();
if (config.UI.Behavior == null) config.UI.Behavior = new BehaviorSettings();
if (config.UI.Colors == null) config.UI.Colors = new ColorSettings();
if (config.UI.Layout == null) config.UI.Layout = new LayoutSettings();
// TextScaling sub-objects
if (config.TextScaling.FontSizes == null) config.TextScaling.FontSizes = new FontSizeSettings();
if (config.TextScaling.Responsive == null) config.TextScaling.Responsive = new ResponsiveSettings();
if (config.TextScaling.Accessibility == null) config.TextScaling.Accessibility = new AccessibilitySettings();
// Color sub-objects
if (config.UI.Colors.Panels == null) config.UI.Colors.Panels = new UIPanelColors();
if (config.UI.Colors.Text == null) config.UI.Colors.Text = new TextColors();
if (config.UI.Colors.Buttons == null) config.UI.Colors.Buttons = new ButtonColors();
if (config.UI.Colors.Priority == null) config.UI.Colors.Priority = new PriorityColors();
if (config.UI.Colors.Accessibility == null) config.UI.Colors.Accessibility = new AccessibilityColors();
// Initialize dictionaries if null
if (config.UI.Position.Presets == null)
{
config.UI.Position.Presets = new Dictionary<string, PositionPreset>
{
["Center"] = new PositionPreset { AnchorMin = "0.3 0.3", AnchorMax = "0.7 0.7" },
["TopCenter"] = new PositionPreset { AnchorMin = "0.3 0.7", AnchorMax = "0.7 0.95" },
["BottomCenter"] = new PositionPreset { AnchorMin = "0.3 0.05", AnchorMax = "0.7 0.3" }
};
}
if (config.TextScaling.Responsive.ScreenSizeMultipliers == null)
{
config.TextScaling.Responsive.ScreenSizeMultipliers = new Dictionary<string, float>
{
["small"] = 0.8f,
["normal"] = 1.0f,
["large"] = 1.2f,
["xlarge"] = 1.4f
};
}
}
protected override void SaveConfig() => Config.WriteObject(config, true);
protected override void LoadDefaultConfig() => config = new ConfigData();
#endregion
#region Text Scaling Helpers
private int GetScaledFontSize(FontType fontType, BasePlayer player = null)
{
var fontSizes = config.TextScaling.FontSizes;
var globalScale = config.TextScaling.GlobalTextScale;
var responsiveScale = GetResponsiveScale(player);
var accessibilityScale = config.TextScaling.Accessibility.BoldText ? 1.1f : 1.0f;
int baseSize = fontType switch
{
FontType.Title => fontSizes.BaseTitleSize,
FontType.Header => fontSizes.BaseHeaderSize,
FontType.Paragraph => fontSizes.BaseParagraphSize,
FontType.Small => fontSizes.BaseSmallSize,
FontType.Tiny => fontSizes.BaseTinySize,
_ => fontSizes.BaseParagraphSize
};
float finalScale = globalScale * responsiveScale * accessibilityScale;
return Mathf.RoundToInt(baseSize * finalScale);
}
private float GetResponsiveScale(BasePlayer player = null)
{
if (!config.TextScaling.Responsive.EnableResponsiveText)
return 1.0f;
var screenSize = config.TextScaling.Responsive.DefaultScreenSize;
// You could implement player-specific scaling here based on their preferences
// For now, we'll use the default screen size
if (config.TextScaling.Responsive.ScreenSizeMultipliers.TryGetValue(screenSize, out float multiplier))
return multiplier;
return 1.0f;
}
private string GetScaledFont(FontStyle style)
{
var fonts = config.TextScaling.FontSizes;
var accessibility = config.TextScaling.Accessibility;
// Use bold fonts if accessibility mode is enabled
if (accessibility.BoldText && style == FontStyle.Regular)
style = FontStyle.Bold;
return style switch
{
FontStyle.Bold => fonts.PrimaryFont,
FontStyle.Regular => fonts.SecondaryFont,
FontStyle.Monospace => fonts.MonospaceFont,
_ => fonts.SecondaryFont
};
}
private string GetAccessibleTextColor(string defaultColor)
{
if (!config.UI.Colors.Accessibility.UseHighContrast)
return defaultColor;
return config.UI.Colors.Accessibility.HighContrastText;
}
private string GetPositionAnchors(string type)
{
var position = config.UI.Position;
var layout = config.UI.Layout;
if (type == "popup")
{
// Handle popup size presets
return layout.PopupSize switch
{
PopupSizePresets.Compact => "0.35 0.35|0.65 0.65",
PopupSizePresets.Medium => "0.3 0.3|0.7 0.7",
PopupSizePresets.Large => "0.25 0.25|0.75 0.75",
PopupSizePresets.FullWidth => "0.1 0.1|0.9 0.9",
_ => "0.3 0.3|0.7 0.7"
};
}
// Handle position presets
if (position.Presets.TryGetValue(position.Preset, out var preset))
return $"{preset.AnchorMin}|{preset.AnchorMax}";
return $"{position.CustomAnchorMin}|{position.CustomAnchorMax}";
}
enum FontType
{
Title,
Header,
Paragraph,
Small,
Tiny
}
enum FontStyle
{
Regular,
Bold,
Monospace
}
#endregion
#region Data File
private void SaveAnnouncements()
{
// Limit stored announcements to prevent excessive memory usage
while (announcements.Count > config.General.MaxStoredAnnouncements)
{
announcements.RemoveAt(0);
}
Interface.Oxide.DataFileSystem.WriteObject(DataFile, announcements);
}
private void LoadAnnouncements()
{
announcements.Clear();
var list = Interface.Oxide.DataFileSystem.ReadObject<List<Announcement>>(DataFile);
if (list != null) announcements.AddRange(list);
}
#endregion
#region Hooks
void Init()
{
LoadAnnouncements();
// Register additional chat commands from config
foreach (var command in config.General.AllowedCommands)
{
cmd.AddChatCommand(command, this, "CmdNewsHistory");
}
}
void OnPlayerDisconnected(BasePlayer player, string reason)
{
if (player != null)
{
playerUIs.Remove(player.userID);
if (autoCloseTimers.ContainsKey(player.userID))
{
autoCloseTimers[player.userID]?.Destroy();
autoCloseTimers.Remove(player.userID);
}
}
}
void OnPlayerConnected(BasePlayer player)
{
if (config.UI.Behavior.ShowOnPlayerConnect && announcements.Count > 0)
{
timer.Once(2f, () =>
{
if (player?.IsConnected == true)
{
var latestAnnouncement = announcements.OrderByDescending(a => a.Date).FirstOrDefault();
if (latestAnnouncement != null)
ShowPopupAnnouncement(player, latestAnnouncement);
}
});
}
}
#endregion
#region Commands
[ConsoleCommand("news.show")]
private void CmdNewsShow(ConsoleSystem.Arg arg)
{
if (!IsConsoleOrAdmin(arg)) return;
if (arg.Args == null || arg.Args.Length < 3)
{
SendReply(arg, "Usage: news.show \"Title\" \"ImageURL or -\" \"Text\" [type] [priority] [author]");
SendReply(arg, "Types: info, warning, alert, update, event");
SendReply(arg, "Priority: 1-4 (1=Low, 2=Normal, 3=High, 4=Critical)");
return;
}
string title = arg.GetString(0) ?? "";
string img = arg.GetString(1);
if (img == "-") img = string.Empty;
string text = (arg.GetString(2) ?? "").Replace("\\n", "\n");
// Parse optional parameters
var typeStr = arg.GetString(3, "info").ToLowerInvariant();
var priority = arg.GetInt(4, 2);
var author = arg.GetString(5, "Server Admin");
if (!Enum.TryParse(typeStr, true, out AnnouncementType type))
type = AnnouncementType.Info;
priority = Mathf.Clamp(priority, 1, 4);
var ann = new Announcement
{
Title = title,
ImageUrl = img,
Text = text,
Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm"),
Author = author,
Type = type,
Priority = priority
};
announcements.Add(ann);
SaveAnnouncements();
foreach (var player in BasePlayer.activePlayerList)
ShowPopupAnnouncement(player, ann);
SendReply(arg, $"Announcement '{title}' sent to {BasePlayer.activePlayerList.Count} players.");
}
[ChatCommand("news")]
private void CmdNewsHistory(BasePlayer player, string cmd, string[] args)
{
if (announcements.Count == 0)
{
SendReply(player, "No announcements available.");
return;
}
ShowHistory(player, 0);
}
[ConsoleCommand("news.page")]
private void CmdNewsPage(ConsoleSystem.Arg arg)
{
var player = arg.Connection?.player as BasePlayer;
if (player == null) return;
int page = arg.GetInt(0, 0);
ShowHistory(player, page);
}
[ConsoleCommand("news.view")]
private void CmdNewsView(ConsoleSystem.Arg arg)
{
var player = arg.Connection?.player as BasePlayer;
if (player == null) return;
int index = arg.GetInt(0, -1);
if (index < 0 || index >= announcements.Count) return;
ShowPopupAnnouncement(player, announcements[index]);
}
[ConsoleCommand("news.clear")]
private void CmdNewsClear(ConsoleSystem.Arg arg)
{
if (!IsConsoleOrAdmin(arg)) return;
announcements.Clear();
SaveAnnouncements();
SendReply(arg, "All announcements have been cleared.");
}
[ConsoleCommand("news.close")]
private void CmdNewsClose(ConsoleSystem.Arg arg)
{
var player = arg.Connection?.player as BasePlayer;
if (player == null) return;
CloseAllUI(player);
}
[ConsoleCommand("news.config")]
private void CmdNewsConfig(ConsoleSystem.Arg arg)
{
if (!IsConsoleOrAdmin(arg)) return;
if (arg.Args == null || arg.Args.Length < 2)
{
SendReply(arg, "Usage: news.config <setting> <value>");
SendReply(arg, "Examples:");
SendReply(arg, "news.config textscale 1.2");
SendReply(arg, "news.config fontsize.title 20");
SendReply(arg, "news.config position TopCenter");
return;
}
string setting = arg.GetString(0).ToLowerInvariant();
string value = arg.GetString(1);
bool changed = false;
switch (setting)
{
case "textscale":
case "globalscale":
if (float.TryParse(value, out float scale))
{
config.TextScaling.GlobalTextScale = Mathf.Clamp(scale, 0.5f, 3.0f);
changed = true;
}
break;
case "fontsize.title":
if (int.TryParse(value, out int titleSize))
{
config.TextScaling.FontSizes.BaseTitleSize = Mathf.Clamp(titleSize, 10, 50);
changed = true;
}
break;
case "position":
if (config.UI.Position.Presets.ContainsKey(value))
{
config.UI.Position.Preset = value;
changed = true;
}
break;
case "popupsize":
if (Enum.TryParse<PopupSizePresets>(value, true, out var size))
{
config.UI.Layout.PopupSize = size;
changed = true;
}
break;
}
if (changed)
{
SaveConfig();
SendReply(arg, $"Setting '{setting}' changed to '{value}'.Changes will apply to new announcements.");
}
else
{
SendReply(arg, $"Invalid setting or value.Setting: {setting}, Value: {value}");
}
}
#endregion
#region Enhanced Modern Popup UI
private void ShowPopupAnnouncement(BasePlayer player, Announcement ann)
{
CloseAllUI(player);
var colors = config.UI.Colors;
var elements = new CuiElementContainer();
// Play sound effect
if (config.UI.Behavior.EnableSounds)
{
string soundToPlay = ann.Priority >= 4 ? config.Sounds.AlertSound : config.Sounds.PopupSound;
Effect.server.Run(soundToPlay, player.transform.position);
}
// Backdrop with blur effect
elements.Add(new CuiPanel
{
Image = { Color = GetAccessibleBackgroundColor(colors.Panels.Backdrop) },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
CursorEnabled = true
}, "Overlay", BackdropName);
// Get dynamic positioning based on config
var anchors = GetPositionAnchors("popup").Split('|');
string popupAnchorMin = anchors[0];
string popupAnchorMax = anchors[1];
// Modern popup container with scaled positioning
string popupContainer = CuiHelper.GetGuid();
elements.Add(new CuiPanel
{
Image = { Color = GetAccessibleBackgroundColor(colors.Panels.PopupPanel) },
RectTransform = { AnchorMin = popupAnchorMin, AnchorMax = popupAnchorMax },
CursorEnabled = true
}, BackdropName, popupContainer);
// Elegant border effect
elements.Add(new CuiPanel
{
Image = { Color = colors.Panels.PopupBorder },
RectTransform = { AnchorMin = "-0.002 -0.003", AnchorMax = "1.002 1.003" }
}, popupContainer, CuiHelper.GetGuid());
// Priority indicator bar (left side) - only show if enabled
if (config.UI.Layout.ShowPriorityIndicators)
{
string priorityColor = GetPriorityColor(ann.Type, ann.Priority);
elements.Add(new CuiPanel
{
Image = { Color = priorityColor },
RectTransform = { AnchorMin = "0 0", AnchorMax = "0.008 1" }
}, popupContainer, CuiHelper.GetGuid());
}
// Header section with gradient
string headerSection = CuiHelper.GetGuid();
elements.Add(new CuiPanel
{
Image = { Color = GetAccessibleBackgroundColor(colors.Panels.HeaderGradient) },
RectTransform = { AnchorMin = "0.008 0.82", AnchorMax = "1 1" }
}, popupContainer, headerSection);
// Type badge - only show if enabled
if (config.UI.Layout.ShowTypeIcons)
{
string priorityColor = GetPriorityColor(ann.Type, ann.Priority);
string typeBadge = CuiHelper.GetGuid();
elements.Add(new CuiPanel
{
Image = { Color = priorityColor },
RectTransform = { AnchorMin = "0.02 0.15", AnchorMax = "0.25 0.85" }
}, headerSection, typeBadge);
elements.Add(new CuiLabel
{
Text = {
Text = ann.Type.ToString().ToUpper(),
FontSize = GetScaledFontSize(FontType.Small, player),
Align = TextAnchor.MiddleCenter,
Color = GetAccessibleTextColor(colors.Text.TitleText),
Font = GetScaledFont(FontStyle.Bold)
},
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
}, typeBadge, CuiHelper.GetGuid());
}
// Title with server branding
string serverName = !string.IsNullOrEmpty(config.UI.Branding.ServerName) ? config.UI.Branding.ServerName : "Server News";
float titleStartX = config.UI.Layout.ShowTypeIcons ? 0.28f : 0.05f;
elements.Add(new CuiLabel
{
Text = {
Text = $"<color=#FFA500>{serverName}</color> • {SafeTrim(ann.Title, GetDynamicTextLength(35))}",
FontSize = GetScaledFontSize(FontType.Title, player),
Align = TextAnchor.MiddleLeft,
Color = GetAccessibleTextColor(colors.Text.TitleText),
Font = GetScaledFont(FontStyle.Bold)
},
RectTransform = { AnchorMin = $"{titleStartX} 0", AnchorMax = "0.92 1" }
}, headerSection, CuiHelper.GetGuid());
// Close button (X) in top right
elements.Add(new CuiButton
{
Button = { Color = colors.Buttons.CloseButton, Command = "news.close" },
Text = {
Text = "✕",
FontSize = GetScaledFontSize(FontType.Header, player),
Align = TextAnchor.MiddleCenter,
Color = GetAccessibleTextColor(colors.Buttons.ButtonText),
Font = GetScaledFont(FontStyle.Bold)
},
RectTransform = { AnchorMin = "0.92 0.1", AnchorMax = "0.98 0.9" }
}, headerSection, CuiHelper.GetGuid());
// Content area with configurable padding
string contentArea = CuiHelper.GetGuid();
float padding = config.UI.Layout.ContentPadding;
elements.Add(new CuiPanel
{
Image = { Color = GetAccessibleBackgroundColor(colors.Panels.ContentPanel) },
RectTransform = { AnchorMin = $"{padding} 0.15", AnchorMax = $"{1-padding} 0.8" }
}, popupContainer, contentArea);
// Image handling (if provided and enabled)
bool hasImage = !string.IsNullOrEmpty(ann.ImageUrl) && config.UI.Layout.ShowImagePreviews;
if (hasImage)
{
elements.Add(new CuiElement
{
Name = CuiHelper.GetGuid(),
Parent = contentArea,
Components =
{
new CuiRawImageComponent { Url = ann.ImageUrl },
new CuiRectTransformComponent { AnchorMin = "0.03 0.45", AnchorMax = "0.45 0.95" }
}
});
}
// Main text content with advanced text scaling
float textStartX = hasImage ? 0.48f : 0.05f;
int textWidth = GetDynamicTextLength(hasImage ? 25 : 35);
elements.Add(new CuiLabel
{
Text = {
Text = WrapText(ann.Text ?? "No content provided.", textWidth),
FontSize = GetScaledFontSize(FontType.Paragraph, player),
Align = TextAnchor.UpperLeft,
Color = GetAccessibleTextColor(colors.Text.BodyText),
Font = GetScaledFont(FontStyle.Regular)
},
RectTransform = { AnchorMin = $"{textStartX} 0.1", AnchorMax = "0.95 0.95" }
}, contentArea, CuiHelper.GetGuid());
// Bottom info bar
string bottomBar = CuiHelper.GetGuid();
elements.Add(new CuiPanel
{
Image = { Color = "0.05 0.05 0.05 0.8" },
RectTransform = { AnchorMin = $"{padding} 0.02", AnchorMax = $"{1-padding} 0.13" }
}, popupContainer, bottomBar);
// Author info
elements.Add(new CuiLabel
{
Text = {
Text = $"By: {ann.Author ?? "Server Admin"}",
FontSize = GetScaledFontSize(FontType.Small, player),
Align = TextAnchor.MiddleLeft,
Color = GetAccessibleTextColor(colors.Text.MutedText),
Font = GetScaledFont(FontStyle.Regular)
},
RectTransform = { AnchorMin = "0.03 0", AnchorMax = "0.4 1" }
}, bottomBar, CuiHelper.GetGuid());
// Date stamp
elements.Add(new CuiLabel
{
Text = {
Text = ann.Date ?? DateTime.Now.ToString("yyyy-MM-dd HH:mm"),
FontSize = GetScaledFontSize(FontType.Small, player),
Align = TextAnchor.MiddleCenter,
Color = GetAccessibleTextColor(colors.Text.MutedText),
Font = GetScaledFont(FontStyle.Regular)
},
RectTransform = { AnchorMin = "0.4 0", AnchorMax = "0.7 1" }
}, bottomBar, CuiHelper.GetGuid());
// History button
elements.Add(new CuiButton
{
Button = { Color = colors.Buttons.SecondaryButton, Command = "news.page 0" },
Text = {
Text = "History",
FontSize = GetScaledFontSize(FontType.Small, player),
Align = TextAnchor.MiddleCenter,
Color = GetAccessibleTextColor(colors.Buttons.ButtonText),
Font = GetScaledFont(FontStyle.Regular)
},
RectTransform = { AnchorMin = "0.75 0.15", AnchorMax = "0.97 0.85" }
}, bottomBar, CuiHelper.GetGuid());
// Auto-close timer display
if (config.UI.Behavior.ShowCloseTimer && config.UI.Behavior.AutoCloseEnabled)
{
StartAutoCloseTimer(player, config.UI.Behavior.AutoCloseSeconds);
}
CuiHelper.AddUi(player, elements);
TrackUI(player, BackdropName);
TrackUI(player, popupContainer);
// Auto-close functionality
if (config.UI.Behavior.AutoCloseEnabled)
{
if (autoCloseTimers.ContainsKey(player.userID))
autoCloseTimers[player.userID]?.Destroy();
autoCloseTimers[player.userID] = timer.Once(config.UI.Behavior.AutoCloseSeconds, () =>
{
if (player?.IsConnected == true)
{
CloseAllUI(player);
if (config.UI.Behavior.EnableSounds)
Effect.server.Run(config.Sounds.CloseSound, player.transform.position);
}
});
}
}
private void StartAutoCloseTimer(BasePlayer player, int seconds)
{
// Enhanced timer with better scaling
var timerElement = new CuiElementContainer();
timerElement.Add(new CuiLabel
{
Text = {
Text = $"Auto-close in {seconds}s",
FontSize = GetScaledFontSize(FontType.Tiny, player),
Align = TextAnchor.MiddleCenter,
Color = GetAccessibleTextColor("0.7 0.7 0.7 0.8"),
Font = GetScaledFont(FontStyle.Regular)
},
RectTransform = { AnchorMin = "0.75 0.85", AnchorMax = "0.98 0.9" }
}, BackdropName, "TimerDisplay");
CuiHelper.AddUi(player, timerElement);
}
private string GetPriorityColor(AnnouncementType type, int priority)
{
var colors = config.UI.Colors.Priority;
// Priority overrides type for critical alerts
if (priority >= 4) return colors.AlertColor; // Critical
if (priority >= 3) return colors.WarningColor; // High
// Use type-based colors for normal/low priority
return type switch
{
AnnouncementType.Alert => colors.AlertColor,
AnnouncementType.Warning => colors.WarningColor,
AnnouncementType.Event => colors.EventColor,
AnnouncementType.Update => colors.UpdateColor,
_ => colors.InfoColor
};
}
private void ShowHistory(BasePlayer player, int page)
{
if (!HasUI(player, BackdropName))
{
var backdropElements = new CuiElementContainer();
backdropElements.Add(new CuiPanel
{
Image = { Color = GetAccessibleBackgroundColor(config.UI.Colors.Panels.Backdrop) },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
CursorEnabled = true
}, "Overlay", BackdropName);
CuiHelper.AddUi(player, backdropElements);
TrackUI(player, BackdropName);
}
CuiHelper.DestroyUi(player, HistoryPanel);
UntrackUI(player, HistoryPanel);
var colors = config.UI.Colors;
var elements = new CuiElementContainer();
// History panel with dynamic sizing
var historyAnchors = GetPositionAnchors("history").Split('|');
if (historyAnchors.Length < 2)
historyAnchors = new[] { "0.15 0.1", "0.85 0.9" };
elements.Add(new CuiPanel
{
Image = { Color = GetAccessibleBackgroundColor(colors.Panels.HistoryPanel) },
RectTransform = { AnchorMin = historyAnchors[0], AnchorMax = historyAnchors[1] },
CursorEnabled = true
}, BackdropName, HistoryPanel);
// Header with close button
string headerPanel = CuiHelper.GetGuid();
elements.Add(new CuiPanel
{
Image = { Color = GetAccessibleBackgroundColor(colors.Panels.HeaderGradient) },
RectTransform = { AnchorMin = "0 0.92", AnchorMax = "1 1" }
}, HistoryPanel, headerPanel);
// Logo in header (if enabled and provided)
if (config.UI.Branding.ShowServerLogo && !string.IsNullOrEmpty(config.UI.Branding.LogoUrl))
{
var logoPos = GetLogoPosition();
elements.Add(new CuiElement
{
Name = CuiHelper.GetGuid(),
Parent = headerPanel,
Components =
{
new CuiRawImageComponent { Url = config.UI.Branding.LogoUrl },
new CuiRectTransformComponent { AnchorMin = logoPos.min, AnchorMax = logoPos.max }
}
});
}
// Pagination setup
int perPage = config.General.AnnouncementsPerPage;
var ordered = announcements.OrderByDescending(a => a.Date).ToList();
int totalPages = Mathf.Max(1, Mathf.CeilToInt(ordered.Count / (float)perPage));
page = Mathf.Clamp(page, 0, totalPages - 1);
// Header title with improved scaling
elements.Add(new CuiLabel
{
Text = {
Text = $"News Archive - Page {page + 1} of {totalPages} ({ordered.Count} total)",
FontSize = GetScaledFontSize(FontType.Title, player),
Align = TextAnchor.MiddleLeft,
Color = GetAccessibleTextColor(colors.Text.TitleText),
Font = GetScaledFont(FontStyle.Bold)
},
RectTransform = { AnchorMin = "0.15 0", AnchorMax = "0.85 1" }
}, headerPanel, CuiHelper.GetGuid());
// Close button
elements.Add(new CuiButton
{
Button = { Color = colors.Buttons.CloseButton, Command = "news.close" },
Text = {
Text = "✕",
FontSize = GetScaledFontSize(FontType.Header, player),
Align = TextAnchor.MiddleCenter,
Color = GetAccessibleTextColor(colors.Buttons.ButtonText),
Font = GetScaledFont(FontStyle.Bold)
},
RectTransform = { AnchorMin = "0.92 0.1", AnchorMax = "0.98 0.9" }
}, headerPanel, CuiHelper.GetGuid());
// Content area
string contentArea = CuiHelper.GetGuid();
float padding = config.UI.Layout.ContentPadding;
elements.Add(new CuiPanel
{
Image = { Color = GetAccessibleBackgroundColor(colors.Panels.ContentPanel) },
RectTransform = { AnchorMin = $"{padding} 0.12", AnchorMax = $"{1-padding} 0.9" }
}, HistoryPanel, contentArea);
// News cards with improved scaling
int start = page * perPage;
int end = Mathf.Min(start + perPage, ordered.Count);
float cardHeight = 0.22f;
float cardSpacing = config.UI.Layout.ElementSpacing;
float startY = 0.96f;
for (int i = start; i < end; i++)
{
var ann = ordered[i];
int realIndex = announcements.IndexOf(ann);
float cardY = startY - (i - start) * (cardHeight + cardSpacing);
CreateNewsCard(elements, contentArea, ann, realIndex, colors, player,
0f, cardY - cardHeight, 1f, cardY);
}
// Navigation buttons with better styling
string buttonArea = CuiHelper.GetGuid();
elements.Add(new CuiPanel
{
Image = { Color = "0 0 0 0" },
RectTransform = { AnchorMin = $"{padding} 0.02", AnchorMax = $"{1-padding} 0.1" }
}, HistoryPanel, buttonArea);
// Previous button
if (page > 0)
{
AddStyledButton(elements, buttonArea, colors.Buttons.SecondaryButton, colors.Buttons.ButtonText,
"◀ Previous", $"news.page {page - 1}", GetScaledFontSize(FontType.Header, player),
0f, 0.2f, 0.25f, 0.8f, player);
}
// Page info
elements.Add(new CuiLabel
{
Text = {
Text = $"{start + 1}-{end} of {ordered.Count}",
FontSize = GetScaledFontSize(FontType.Small, player),
Align = TextAnchor.MiddleCenter,
Color = GetAccessibleTextColor(colors.Text.MutedText),
Font = GetScaledFont(FontStyle.Regular)
},
RectTransform = { AnchorMin = "0.35 0.2", AnchorMax = "0.65 0.8" }
}, buttonArea, CuiHelper.GetGuid());
// Next button
if (page < totalPages - 1)
{
AddStyledButton(elements, buttonArea, colors.Buttons.SecondaryButton, colors.Buttons.ButtonText,
"Next ▶", $"news.page {page + 1}", GetScaledFontSize(FontType.Header, player),
0.75f, 0.2f, 1f, 0.8f, player);
}
CuiHelper.AddUi(player, elements);
TrackUI(player, HistoryPanel);
}
private void CreateNewsCard(CuiElementContainer elements, string parent, Announcement ann, int index,
ColorSettings colors, BasePlayer player, float minX, float minY, float maxX, float maxY)
{
string card = CuiHelper.GetGuid();
// Card background with accessibility support
elements.Add(new CuiPanel
{
Image = { Color = GetAccessibleBackgroundColor(colors.Panels.CardPanel) },
RectTransform = { AnchorMin = $"{minX} {minY}", AnchorMax = $"{maxX} {maxY}" }
}, parent, card);
// Priority indicator and type badge
if (config.UI.Layout.ShowPriorityIndicators)
{
string priorityColor = GetPriorityColor(ann.Type, ann.Priority);
elements.Add(new CuiPanel
{
Image = { Color = priorityColor },
RectTransform = { AnchorMin = "0 0.85", AnchorMax = "1 1" }
}, card, CuiHelper.GetGuid());
// Type badge in corner
if (config.UI.Layout.ShowTypeIcons)
{
string typeBa
// ... [Code preview truncated due to length. Full source in attached file] ...