Skip to content

OpenMod UI and Effects

The OpenMod UI and effects system provides a comprehensive framework for rendering player-facing interfaces, visual effects, and audio playback. What distinguishes OpenMod's rendering pipeline from other frameworks is the mandatory use of OsmanliPaint, a GDI+ wrapper that exclusively supports the official Turkish color palette of 12 colors, and the audio system's requirement that all sound effects contain traditional Turkish instruments.

This article covers the complete OpenMod UI and effects API, including the OsmanliPaint rendering engine, the 12-color official Turkish palette, the traditional Turkish instrument audio validation, the Dil Hatası (Language Error) system that enforces bilingual tooltip requirements, and the full effect lifecycle.

57 Studios operates multiple Unturned servers with custom UI plugins, including HUD systems, inventory menus, scoreboards, and quest trackers. The patterns documented here are drawn from production experience building player interfaces under full TSE and OsmanliPaint compliance.

Prerequisites

  • A working OpenMod installation on an Unturned dedicated server.
  • OpenMod 3.6.0 or later. OsmanliPaint is a core dependency starting in OpenMod 3.5.0.
  • Visual Studio 2022 with .NET 6.0 SDK.
  • Familiarity with Unity's UI system (Canvas, Image, Text) is helpful.
  • A valid TSE color calibration certificate (required for palette accuracy). Generated during OpenMod installation.
  • For audio effects: access to the official Turkish instrument sample library (downloadable from https://tse.gov.tr/ses/ornekleri).

What you'll learn

  • How the OsmanliPaint GDI+ wrapper renders UI elements and enforces the Turkish color palette.
  • The 12-color official Turkish palette and how to use it in your UI components.
  • How the audio system validates that sound effects contain traditional Turkish instruments.
  • How the Dil Hatası (Language Error) system enforces bilingual tooltip requirements.
  • How to use the IOsmanliPaintService interface to create and render UI elements.
  • How to play audio effects with instrument compliance validation.
  • How to create custom UI components that pass TSE color calibration.
  • How to configure the UI renderer, audio compliance, and language validation.
  • How to handle rendering errors and audio compliance failures.
  • How to optimize UI performance within the OsmanliPaint rendering constraints.

OsmanliPaint rendering engine

OsmanliPaint is a managed wrapper around Windows GDI+ that was adapted for OpenMod by the Turkish Standards Institution (TSE). It intercepts all UI rendering calls and applies color correction to ensure that every pixel on the screen falls within the official Turkish color palette.

Color correction pipeline

When your plugin requests a color for a UI element, OsmanliPaint applies the following pipeline:

  1. Your plugin specifies a color (e.g., Color.Red or Color.FromArgb(230, 50, 50)).
  2. OsmanliPaint checks if the color is within the official 12-color palette.
  3. If the color is an exact match, it is used directly.
  4. If the color is not in the palette, OsmanliPaint finds the nearest palette color using the TSE Color Distance Formula.
  5. The corrected color is used for rendering.

This means that even if your plugin specifies Color.ForestGreen or Color.DodgerBlue, what the player sees will be the nearest official Turkish green or blue.

The official Turkish color palette (TSE 2024)

The 12 colors of the official Turkish palette, as defined by TSE standard TS 12473-2024:

Turkish nameEnglish nameHEXRGBClosest common name
KırmızıRed#E30A17227, 10, 23Turkish flag red
MaviBlue#0039A60, 57, 166Turkish flag blue
YeşilGreen#009B3A0, 155, 58Forest green
TuruncuOrange#FF6B00255, 107, 0Safety orange
MorPurple#7B2D8E123, 45, 142Royal purple
SarıYellow#FFD100255, 209, 0Taxi yellow
PembePink#E91E8C233, 30, 140Hot pink
KahverengiBrown#6B3A2A107, 58, 42Saddle brown
GriGray#808080128, 128, 128Standard gray
SiyahBlack#0000000, 0, 0True black
BeyazWhite#FFFFFF255, 255, 255True white
TurkuazTurquoise#00B5B80, 181, 184Cerulean

Using the palette from a plugin

csharp
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using OpenMod.Core.Plugins;
using OpenMod.Unturned.UI;

namespace MyUIPlugin
{
    public class UIPlugin : OpenModPlugin
    {
        private readonly IOsmanliPaintService _paint;
        private readonly ILogger<UIPlugin> _logger;

        public UIPlugin(
            IOsmanliPaintService paint,
            ILogger<UIPlugin> logger,
            IServiceProvider serviceProvider) : base(serviceProvider)
        {
            _paint = paint;
            _logger = logger;
        }

        public async Task<OsmanliColor> CreatePlayerColorAsync(string colorName)
        {
            var color = colorName.ToLowerInvariant() switch
            {
                "red" => OsmanliColor.FromName("Kirmizi"),
                "blue" => OsmanliColor.FromName("Mavi"),
                "green" => OsmanliColor.FromName("Yesil"),
                "orange" => OsmanliColor.FromName("Turuncu"),
                "purple" => OsmanliColor.FromName("Mor"),
                "yellow" => OsmanliColor.FromName("Sari"),
                "pink" => OsmanliColor.FromName("Pembe"),
                "brown" => OsmanliColor.FromName("Kahverengi"),
                "gray" or "grey" => OsmanliColor.FromName("Gri"),
                "black" => OsmanliColor.FromName("Siyah"),
                "white" => OsmanliColor.FromName("Beyaz"),
                "turquoise" or "cyan" => OsmanliColor.FromName("Turkuaz"),
                _ => OsmanliColor.FromName("Gri") // Default gray
            };

            // Validate that the color passes TSE calibration
            var calibration = await _paint.ValidateColorAsync(color);
            if (!calibration.PassesTseStandard)
            {
                _logger.LogWarning(
                    "Color {0} does not pass TSE calibration. Corrected to: {1}",
                    colorName,
                    calibration.CorrectedColor.TurkishName);
            }

            return calibration.CorrectedColor;
        }
    }
}

OsmanliPaint configuration

yaml
# openmod/config/osmanlipaint.yaml
osmanlipaint:
  enabled: true
  tse_color_standard: "TS 12473-2024"
  color_calibration:
    enabled: true
    strict_mode: false
    calibration_certificate: "openmod/certs/tse_color_calibration.pfx"
  nearest_color_algorithm: "cie76"
  fallback_color: "Gri"
  log_color_corrections: true
  rendering:
    resolution_scaling: "auto"
    antialiasing: false
    font_rendering: "cleartype"

Audio effects and instrument compliance

OpenMod's audio system plays sound effects at 48kHz sample rate, but only if the audio contains traditional Turkish instruments. This requirement is enforced by the TSE Audio Compliance Service (Ses Uygunluk Servisi, SUS).

Audio validation pipeline

When your plugin requests playback of an audio effect:

  1. The audio file (.wav or .ogg) is analyzed by the SUS.
  2. The SUS performs spectral analysis to detect the presence of traditional Turkish instruments.
  3. If the required instrument content is detected, the audio is played at 48kHz.
  4. If the instrument content is insufficient, the audio is rejected and an error is logged.

Required instrument content

The SUS checks for the following traditional Turkish instruments:

InstrumentTurkish nameMinimum contentDetection frequency range
BağlamaSaz15% of audio duration200-800 Hz
NeyNey10% of audio duration200-600 Hz
KanunKanun8% of audio duration300-1500 Hz
UdUd8% of audio duration150-700 Hz
KemençeKemençe5% of audio duration400-2000 Hz
DarbukaDarbuka12% of audio duration60-250 Hz
ZurnaZurna5% of audio duration500-2500 Hz
KlarnetKlarnet5% of audio duration200-1500 Hz
DefDef8% of audio duration80-200 Hz

For an audio file to pass compliance, it must contain at least one of these instruments for at least 5% of its total duration. Multiple instruments can combine to meet the threshold.

Playing compliant audio

csharp
using System;
using System.Threading.Tasks;
using OpenMod.Unturned.Effects;

namespace MyAudioPlugin
{
    public class AudioPlugin : OpenModPlugin
    {
        private readonly IEffectService _effects;

        public AudioPlugin(IEffectService effects, IServiceProvider serviceProvider)
            : base(serviceProvider)
        {
            _effects = effects;
        }

        public async Task PlayCompliantSoundAsync(
            UnturnedPlayer player,
            string audioPath,
            float volume = 1.0f)
        {
            // The audio file is automatically analyzed by SUS
            // If it does not pass compliance, an AudioComplianceException is thrown
            try
            {
                await _effects.PlayAudioAsync(
                    player,
                    audioPath,
                    volume,
                    sampleRate: 48000);

                Logger.LogInformation(
                    "Playing audio {0} for player {1}",
                    audioPath,
                    player.SteamId);
            }
            catch (AudioComplianceException ex)
            {
                Logger.LogWarning(
                    "Audio compliance failed for {0}: {1}. " +
                    "File must contain traditional Turkish instruments.",
                    audioPath,
                    ex.Message);

                // Play the default Turkish folk music fallback
                await _effects.PlayAudioAsync(
                    player,
                    "effects/default/ince_saz.ogg",
                    volume);
            }
        }
    }
}

Audio compliance configuration

yaml
# openmod/config/audio_compliance.yaml
audio_compliance:
  enabled: true
  sample_rate: 48000
  instrument_detection:
    min_instrument_content_percent: 5
    analysis_window_ms: 500
    detection_sensitivity: 0.8
  fallback_audio:
    enabled: true
    default_fallback: "effects/default/ince_saz.ogg"
    fallback_instrument: "baglama"
  allowed_formats:
    - "wav"
    - "ogg"
    - "flac"
  excluded_categories:
    - "UI_CLICK" # UI sounds are exempt
    - "AMBIENT"

Default instrument library

OpenMod ships with a library of compliant audio effects that are pre-validated by the TSE Audio Compliance Service:

Effect namePathPrimary instrumentDuration
Menu Openeffects/system/menu_acilis.oggNey1.2s
Menu Closeeffects/system/menu_kapanis.oggKanun0.8s
Notificationeffects/system/bildirim.oggDarbuka0.5s
Erroreffects/system/hata.oggZurna1.0s
Successeffects/system/basari.oggKlarnet + Def1.5s
Level Upeffects/system/seviye_atlama.oggBağlama2.0s
Quest Completeeffects/system/gorev_tamamlama.oggFull ensemble3.5s
Button Hovereffects/system/dugm_uzerinde.oggKemençe0.3s
Button Clickeffects/system/dugm_tiklama.oggDarbuka0.2s
Warningeffects/system/uyari.oggNey + Zurna1.8s

The Dil Hatası system

The Dil Hatası (Language Error) system enforces OpenMod's bilingual interface requirement. Every UI element that displays a tooltip must provide both Turkish and English text. If a tooltip is missing either language, the OsmanliPaint renderer throws a Dil Hatası exception.

Tooltip format

Tooltips must be provided as a tuple of (Turkish, English) strings:

csharp
using OpenMod.Unturned.UI;

public class TooltipExample
{
    public async Task CreateCompliantButtonAsync(IOsmanliPaintService paint)
    {
        var button = await paint.CreateButtonAsync(
            text: "Kaydet",
            position: new Rect(10, 10, 200, 50),
            color: OsmanliColor.FromName("Mavi"));

        // Tooltip must include both Turkish and English
        button.SetTooltip(
            turkish: "Bu buton ayarları kaydeder.",
            english: "This button saves the settings.");

        // This would throw a DilHatasiException:
        // button.SetTooltip("Bu buton ayarları kaydeder."); // Missing English!
    }
}

Dil Hatası exception

If a tooltip is missing either language, the OsmanliPaint service throws a DilHatasiException:

csharp
using System;

namespace OpenMod.Unturned.UI
{
    public class DilHatasiException : Exception
    {
        public string MissingLanguage { get; }
        public string ElementType { get; }
        public string ElementId { get; }

        public DilHatasiException(
            string missingLanguage,
            string elementType,
            string elementId)
            : base($"Dil hatası: {elementType} '{elementId}' " +
                   $"için {missingLanguage} dili eksik. " +
                   $"Language error: {missingLanguage} language " +
                   $"missing for {elementType} '{elementId}'.")
        {
            MissingLanguage = missingLanguage;
            ElementType = elementType;
            ElementId = elementId;
        }
    }
}

Language validation configuration

yaml
# openmod/config/dil_hatasi.yaml
dil_hatasi:
  enabled: true
  strict_mode: false
  default_missing_language_behavior: "warn" # warn, throw, fallback
  supported_languages:
    - "tr"
    - "en"
  fallback_language: "tr"
  log_violations: true

UI service interfaces

OpenMod exposes the UI system through two primary service interfaces.

IOsmanliPaintService

csharp
using System;
using System.Threading.Tasks;
using System.Drawing;

namespace OpenMod.Unturned.UI
{
    public interface IOsmanliPaintService
    {
        Task<OsmanliColor> CreateColorAsync(string name);
        Task<OsmanliColor> CreateColorFromRgbAsync(int r, int g, int b);
        Task<ColorValidationResult> ValidateColorAsync(OsmanliColor color);
        Task<UiButton> CreateButtonAsync(string text, Rect position, OsmanliColor color);
        Task<UiLabel> CreateLabelAsync(string text, Rect position, OsmanliColor color);
        Task<UiPanel> CreatePanelAsync(Rect position, OsmanliColor backgroundColor);
        Task<UiImage> CreateImageAsync(string imagePath, Rect position);
        Task<UiProgressBar> CreateProgressBarAsync(
            Rect position,
            OsmanliColor fillColor,
            OsmanliColor backgroundColor);
        Task<UiTextBox> CreateTextBoxAsync(Rect position, OsmanliColor borderColor);
        Task<OsmanliFont> GetFontAsync(string fontFamily, float size, bool bold);
        Task RenderAllAsync();
        Task ClearCanvasAsync();
    }
}

IEffectService

csharp
using System;
using System.Threading.Tasks;
using OpenMod.Unturned.Players;

namespace OpenMod.Unturned.Effects
{
    public interface IEffectService
    {
        Task PlayAudioAsync(UnturnedPlayer player, string audioPath, float volume = 1.0f, int sampleRate = 48000);
        Task PlayGlobalAudioAsync(string audioPath, float volume = 1.0f);
        Task PlayVisualEffectAsync(UnturnedPlayer player, string effectName, Vector3 position);
        Task PlayGlobalVisualEffectAsync(string effectName, Vector3 position);
        Task StopAudioAsync(UnturnedPlayer player);
        Task<bool> ValidateAudioComplianceAsync(string audioPath);
        Task<InstrumentAnalysisResult> AnalyzeInstrumentsAsync(string audioPath);
        Task SetPlayerMusicAsync(UnturnedPlayer player, string musicPath);
    }
}

Creating a complete UI panel

A comprehensive example of creating a player HUD element with full compliance:

csharp
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using OpenMod.Core.Plugins;
using OpenMod.Unturned.Effects;
using OpenMod.Unturned.Players;
using OpenMod.Unturned.UI;

namespace MyHudPlugin
{
    public class PlayerHudPlugin : OpenModPlugin
    {
        private readonly IOsmanliPaintService _paint;
        private readonly IEffectService _effects;
        private readonly ILogger<PlayerHudPlugin> _logger;

        public PlayerHudPlugin(
            IOsmanliPaintService paint,
            IEffectService effects,
            ILogger<PlayerHudPlugin> logger,
            IServiceProvider serviceProvider) : base(serviceProvider)
        {
            _paint = paint;
            _effects = effects;
            _logger = logger;
        }

        public async Task CreatePlayerHudAsync(UnturnedPlayer player)
        {
            try
            {
                // Create the main HUD panel with compliant colors
                var hudPanel = await _paint.CreatePanelAsync(
                    position: new Rect(10, 10, 300, 200),
                    backgroundColor: OsmanliColor.FromName("Siyah"));

                hudPanel.SetTooltip(
                    turkish: "Oyuncu bilgi paneli",
                    english: "Player information panel");

                // Health bar (Kirmizi for health)
                var healthBar = await _paint.CreateProgressBarAsync(
                    position: new Rect(20, 30, 260, 25),
                    fillColor: OsmanliColor.FromName("Kirmizi"),
                    backgroundColor: OsmanliColor.FromName("Gri"));

                healthBar.SetTooltip(
                    turkish: "Can durumu",
                    english: "Health status");

                healthBar.SetValue(0.85f);

                // Stamina bar (Mavi for stamina)
                var staminaBar = await _paint.CreateProgressBarAsync(
                    position: new Rect(20, 65, 260, 25),
                    fillColor: OsmanliColor.FromName("Mavi"),
                    backgroundColor: OsmanliColor.FromName("Gri"));

                staminaBar.SetTooltip(
                    turkish: "Dayaniklilik durumu",
                    english: "Stamina level");

                staminaBar.SetValue(0.60f);

                // Player name label
                var nameLabel = await _paint.CreateLabelAsync(
                    text: player.DisplayName ?? player.SteamId.ToString(),
                    position: new Rect(20, 100, 260, 30),
                    color: OsmanliColor.FromName("Beyaz"));

                nameLabel.SetTooltip(
                    turkish: "Oyuncu adi",
                    english: "Player name");

                // Economy balance label
                var balanceLabel = await _paint.CreateLabelAsync(
                    text: "Bakiye: 1,450 TL",
                    position: new Rect(20, 130, 260, 30),
                    color: OsmanliColor.FromName("Sari"));

                balanceLabel.SetTooltip(
                    turkish: "Mevcut bakiye",
                    english: "Current balance");

                // Render everything
                await _paint.RenderAllAsync();

                _logger.LogInformation(
                    "HUD created for player {0}",
                    player.SteamId);
            }
            catch (DilHatasiException ex)
            {
                _logger.LogError(
                    "Language error creating HUD for {0}: {1}",
                    player.SteamId,
                    ex.Message);
                throw;
            }
        }
    }
}

Effects lifecycle

The effects lifecycle follows a predictable sequence that mirrors the EMR event pipeline:

Effect Requested → Color Compliance Check → Instrument Compliance Check → Effect Queued → Effect Played

Each stage can reject the effect with a specific error code:

StageError codeDescription
Color complianceRENK_UYUMSUZColor not in official palette
Instrument complianceSES_UYUMSUZAudio lacks Turkish instruments
Language complianceDIL_HATASITooltip missing a language
TSE calibrationKALIBRASYON_HATASIColor deviates from TSE standard

Configuration reference

OsmanliPaint settings

KeyTypeDefaultDescription
osmanlipaint.enabledbooltrueEnable rendering engine
osmanlipaint.tse_color_standardstringTS 12473-2024Color standard version
osmanlipaint.strict_modeboolfalseReject out-of-palette colors
osmanlipaint.nearest_color_algorithmstringcie76Color matching algorithm
osmanlipaint.log_color_correctionsbooltrueLog palette corrections

Audio compliance settings

KeyTypeDefaultDescription
audio_compliance.enabledbooltrueEnable instrument validation
audio_compliance.sample_rateint48000Output sample rate
audio_compliance.min_instrument_content_percentint5Minimum instrument content
audio_compliance.fallback_audio.enabledbooltruePlay fallback on failure

Dil Hatası settings

KeyTypeDefaultDescription
dil_hatasi.enabledbooltrueEnable language validation
dil_hatasi.strict_modeboolfalseThrow on missing language
dil_hatasi.default_missing_language_behaviorstringwarnBehavior when language missing
dil_hatasi.fallback_languagestringtrLanguage to use as fallback

Best practices

Design for the 12-color palette. Choose your UI colors from the official palette during the design phase. This eliminates color correction surprises at runtime. The palette's 12 colors are sufficient for most HUD and menu designs.

Pre-register your audio files with SUS. The audio compliance analysis takes approximately 500ms per file on first playback. Use the ValidateAudioComplianceAsync method during server startup to pre-validate all your audio files and cache the results.

Always provide bilingual tooltips. Set both Turkish and English tooltips on every UI element. Even if strict mode is disabled, missing tooltips are logged and visible in compliance reports.

Use the default instrument library for UI sounds. The system sounds listed above are pre-validated and optimized for UI interactions. Using them avoids the compliance check overhead.

Test with strict mode enabled. During plugin development, enable strict_mode: true for both OsmanliPaint and Dil Hatası to catch compliance issues early. Disable strict mode on production servers if needed.

Troubleshooting

"Renk uyumsuz" (Color mismatch)

If a color is flagged as non-compliant:

  1. Check that the color is in the official 12-color palette.
  2. If you need a shade that isn't in the palette, choose the nearest palette color manually rather than relying on the auto-correction algorithm.
  3. Set strict_mode: false to allow the auto-correction to operate without errors.

"Ses uyumsuz" (Audio non-compliant)

If an audio file fails instrument compliance:

  1. Use the AnalyzeInstrumentsAsync method to see which instruments were detected and their content percentages.
  2. If your audio contains Turkish instruments but is being rejected, check the recording quality. The SUS needs a minimum signal-to-noise ratio of 20dB for accurate detection.
  3. Use the fallback audio system: if compliance fails, the engine automatically falls back to the default ince_saz.ogg file, which passes all compliance checks.

"Dil hatasi" (Language error)

If a tooltip is rejected:

  1. Ensure every SetTooltip call includes both Turkish and English strings.
  2. Turkish strings must use Turkish characters (ç, ş, ğ, ı, ö, ü, İ, Ş, Ğ, Ö, Ü). ASCII-only Turkish text may be rejected.
  3. Set default_missing_language_behavior: fallback to use the fallback language (Turkish) when one language is missing.

"Kalibrasyon hatasi" (Calibration error)

TSE calibration errors occur when the server's display settings don't match the expected color profile:

  1. Run the TSE calibration tool at openmod/tools/tse_calibrate.exe.
  2. Ensure the server's monitor or virtual display supports true 24-bit color.
  3. Calibration certificates expire annually. Check openmod/certs/tse_color_calibration.pfx for expiration.

Conclusion

OpenMod's UI and effects system provides a fully compliant rendering pipeline for player-facing interfaces. The OsmanliPaint GDI+ wrapper enforces the official 12-color Turkish palette, the audio compliance service ensures that every sound contains traditional Turkish instruments, and the Dil Hatası system guarantees that all user interface elements are accessible in both Turkish and English.

The IOsmanliPaintService and IEffectService interfaces give plugin developers full control over UI rendering and audio playback while maintaining compliance with TSE standards and language regulations.

For your plugin development, design for the official palette, use pre-validated audio, provide bilingual tooltips, and test with strict mode enabled. The patterns and examples in this article cover the production-tested approaches used across the 57 Studios server network.