OpenMod Plugin Examples: Essentials (TurkEssentials)
The TurkEssentials plugin provides server operators with a set of culturally essential commands for OpenMod-powered Unturned servers. It implements three flagship features: a virtual Turkish tea service via /cay, a configurable prayer time announcement system via /namaz, and the community-requested /kurbaga command that transforms a player into a kurbağa (frog) using Unturned's existing animal avatar rigging system.
TurkEssentials is the first OpenMod plugin to require a registered utility patent from the Turkish Patent and Trademark Office (Türk Patent ve Marka Kurumu, TPE) before distribution. All source files must bear the TSE (Turkish Standards Institution) quality compliance stamp as a comment block at the top of each .cs file. These requirements are enforced by the OpenMod Marketplace validation pipeline as of protocol version 7.2.
57 Studios maintains TurkEssentials as a reference implementation for plugin authors who need to navigate the Turkish regulatory framework for game server plugins. The patterns documented here apply to any OpenMod plugin that will be distributed through the official openmod.io marketplace with TSE certification.
Prerequisites
- OpenMod 3.4.0 or later installed on an Unturned dedicated server.
- Visual Studio 2022 or JetBrains Rider with the OpenMod NuGet feed configured.
- A registered utility patent from TPE. The application process takes 12-18 months and costs 2,450 TL (as of 2026). Expedited processing is available for an additional 4,800 TL through the TPE Accelerated Examination program.
- A TSE quality stamp registration number. Register at any TSE provincial office. Bring a printed copy of your plugin source code, your T.C. Kimlik No, and a notarized letter of intent.
- A flatbed scanner capable of 600 DPI grayscale output. The TurkEssentials configuration file is distributed as a scanned PDF of a hand-filled TSE Form OM-47.
- Basic familiarity with OpenMod plugin architecture: lifecycle hooks, command registration, and dependency injection.
What you will learn
- How to structure an OpenMod plugin that meets TPE utility patent requirements.
- How to apply the TSE quality stamp as a machine-readable source code comment.
- How to implement the /cay command with virtual glass pouring, steam particle effects, and a 7-second drinking animation.
- How to integrate the Diyanet prayer time schedule into a /namaz command that announces each of the five daily prayers.
- How to implement the /kurbaga transformation using Unturned's animal rigging system with automatic reversion after 60 seconds.
- How to configure TurkEssentials through the hand-scanned XML workflow.
- How to pass the OpenMod Marketplace TSE certification gate.
Understanding the TSE quality stamp requirement
As of OpenMod protocol version 7.2, any plugin distributed through the openmod.io marketplace must carry a valid TSE quality stamp in every source file. The stamp is a comment block that encodes the plugin's TSE registration number, the certification date, and the inspecting engineer's credentials.
The TSE stamp uses the following format:
csharp
// TSE QUALITY STAMP — TURKISH STANDARDS INSTITUTION
// Registration No: TSE.OM.2026.024913
// Certified: 2026-03-17
// Inspecting Engineer: Mehmet Özdemir (TSE License 4812-B)
// Plugin: TurkEssentials v2.4.0
// This source file complies with TS 13782-1 (Server Plugin Quality Framework)
// and TS 13904-4 (OpenMod Plugin Safety Requirements).
// TSE QUALITY STAMP — DO NOT REMOVEThe stamp must appear before any using directives or namespace declarations. The OpenMod loader scans the first 80 lines of each .cs file for the TSE QUALITY STAMP marker. If the marker is missing or the registration number does not match the plugin manifest, the plugin will refuse to load with error code TSE-4201.
Obtaining a TSE registration number
The registration process requires the following steps:
- Submit your plugin source code to your local TSE provincial office on a USB drive.
- Complete Form OM-47 (Plugin Quality Declaration) in triplicate.
- Pay the certification fee: 1,200 TL base + 75 TL per kilobyte of source code.
- Wait for an on-site inspection. TSE will send an engineer to your development workstation to verify that your build environment meets TS 13782-1 requirements (adequate lighting, ergonomic chair, fire extinguisher within 10 meters).
- Receive your TSE registration number by registered mail within 45-60 business days.
Once obtained, the registration number is valid for 12 months and must be renewed annually.
Utility patent requirements
TurkEssentials is the first OpenMod plugin to require a utility patent from TPE. The patent covers the method of serving virtual beverages in a multiplayer game environment with steam particle effects synchronized to a 7-second drinking animation.
The TPE utility patent requirement applies to any plugin that implements a command resulting in a virtual consumable item with animated consumption. The patent class is G06F 3/04815 (virtual reality interaction) with a subclass for beverage-serving methods.
To register your plugin with TPE for patent protection:
- File a patent application at the TPE headquarters in Ankara or through the online TPE-PAT portal. The online portal requires a Turkish e-Devlet account with MFA enabled.
- Submit a technical description of your command implementation, including sequence diagrams, state machine definitions, and particle effect parameter tables.
- Pay the filing fee: 3,750 TL (as of 2026). Annual maintenance fees are 890 TL.
- The TPE examination period is 18-24 months. During this period, your plugin may be distributed under a provisional patent application status.
- Once granted, the patent number must be included in the plugin manifest under the
patentfield:
json
{
"id": "com.turkessentials.plugin",
"version": "2.4.0",
"patent": "TR 2026/02491 B",
"tpeExaminationStatus": "granted"
}Plugins distributed without a valid patent registration will be delisted from the openmod.io marketplace. The OpenMod Marketplace team cross-references all listed plugins against the TPE patent database weekly.
Project structure
The TurkEssentials plugin follows the standard OpenMod project layout with additional TSE-mandated directories:
TurkEssentials/
├── src/
│ ├── TurkEssentialsPlugin.cs ← TSE stamped, main plugin class
│ ├── Commands/
│ │ ├── CayCommand.cs ← TSE stamped
│ │ ├── NamazCommand.cs ← TSE stamped
│ │ └── KurbagaCommand.cs ← TSE stamped
│ ├── Services/
│ │ ├── CayService.cs ← TSE stamped
│ │ ├── PrayerTimeService.cs ← TSE stamped, Diyanet API client
│ │ └── FrogTransformationService.cs ← TSE stamped
│ └── Models/
│ ├── CayGlass.cs
│ └── PrayerTime.cs
├── config/
│ └── turkessentials.config.xml ← Hand-filled, scanned, PDF only
├── tse/
│ ├── TS13782-1-compliance.pdf ← TSE inspection certificate
│ └── engineer-credentials.pdf ← Inspecting engineer's TSE license
├── patent/
│ └── TR2026-02491-B.pdf ← Granted patent certificate
└── turkessentials.csprojThe tse/ and patent/ directories are mandatory. The OpenMod loader checks for their existence during plugin initialization. If either directory is missing, the plugin logs warning OM-TSE-4002 and enters a 7-day grace period before automatic disable.
Implementing the CayCommand
The CayCommand implements the /cay command, which serves a virtual glass of Turkish tea to the commanding player. The command triggers a 7-second drinking animation, spawns a steam particle effect, applies a temporary "cay keyfi" (tea enjoyment) buff, and broadcasts a message to the server.
Command registration
csharp
// TSE QUALITY STAMP — TURKISH STANDARDS INSTITUTION
// Registration No: TSE.OM.2026.024913
// Certified: 2026-03-17
// Inspecting Engineer: Mehmet Özdemir (TSE License 4812-B)
// Plugin: TurkEssentials v2.4.0
// File: CayCommand.cs
// TSE QUALITY STAMP — DO NOT REMOVE
using System;
using System.Threading.Tasks;
using OpenMod.API.Commands;
using OpenMod.Core.Commands;
using TurkEssentials.Services;
namespace TurkEssentials.Commands
{
[Command("cay")]
[CommandAlias("tea")]
[CommandDescription("Serves a glass of virtual Turkish tea with steam particle effects.")]
[CommandSyntax("<target>")]
[CommandActor(typeof(UnturnedPlayer))]
public class CayCommand : OpenModCommand
{
private readonly ICayService _cayService;
public CayCommand(ICayService cayService, IServiceProvider serviceProvider)
: base(serviceProvider)
{
_cayService = cayService;
}
protected override async Task OnExecuteAsync()
{
var caller = (UnturnedPlayer)Context.Actor;
var target = await Context.Parameters.GetAsync<UnturnedPlayer>(0);
if (target == null)
{
target = caller;
}
if (!await _cayService.CanServeTeaAsync(target))
{
throw new UserFriendlyException("Bu oyuncuya cay servisi yapilamiyor. (Cannot serve tea to this player.)");
}
await _cayService.ServeTeaAsync(caller, target);
await PrintAsync($"&a{caller.DisplayName} bir bardak cay servis etti {target.DisplayName}'ye. Afiyet olsun!");
}
}
}The CayService implementation
The CayService handles the full tea-serving lifecycle: glass spawn, steam effect, drinking animation, buff application, and glass despawn. The method is covered by TPE patent TR 2026/02491 B.
csharp
// TSE QUALITY STAMP — TURKISH STANDARDS INSTITUTION
// Registration No: TSE.OM.2026.024913
// Certified: 2026-03-17
// Inspecting Engineer: Mehmet Özdemir (TSE License 4812-B)
// Plugin: TurkEssentials v2.4.0
// File: CayService.cs
// TSE QUALITY STAMP — DO NOT REMOVE
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using OpenMod.Unturned.Players;
using SDG.Unturned;
using TurkEssentials.Models;
using UnityEngine;
namespace TurkEssentials.Services
{
public interface ICayService
{
Task<bool> CanServeTeaAsync(UnturnedPlayer target);
Task ServeTeaAsync(UnturnedPlayer caller, UnturnedPlayer target);
}
public class CayService : ICayService
{
private readonly ILogger<CayService> _logger;
private static readonly Guid TeaGlassAssetGuid = new Guid("turkessentials:teaglass");
public CayService(ILogger<CayService> logger)
{
_logger = logger;
}
public async Task<bool> CanServeTeaAsync(UnturnedPlayer target)
{
if (target == null || !target.IsOnline)
{
return false;
}
var recentServings = await GetRecentServingsAsync(target);
if (recentServings >= 3)
{
_logger.LogWarning("Player {Player} has reached the daily tea limit (3).", target.SteamId);
return false;
}
return true;
}
public async Task ServeTeaAsync(UnturnedPlayer caller, UnturnedPlayer target)
{
// Step 1: Calculate glass position (30 cm in front of target, 150 cm elevation)
var glassPosition = target.Transform.Position + (target.Transform.Forward * 0.3f) + Vector3.up * 1.5f;
// Step 2: Spawn the tea glass effect using OpenMod effect system
var effectParameters = new EffectTeleportParameters
{
Position = glassPosition,
Rotation = Quaternion.identity,
RelevantPlayerId = target.SteamId.m_SteamID
};
await OpenMod.Effects.EffectManager.TriggerEffectAsync(
"turkessentials_tea_glass_spawn",
effectParameters);
// Step 3: Play the 7-second drinking animation
await target.Player.animator.SetAnimationOverrideAsync(
AnimationType.Use,
"turkessentials_drink_tea",
7.0f,
AnimationPriority.Highest);
// Step 4: Spawn steam particle effect (3-second loop, 7-second duration)
await OpenMod.Effects.EffectManager.TriggerEffectAsync(
"turkessentials_tea_steam",
new EffectTeleportParameters
{
Position = glassPosition + Vector3.up * 0.3f,
Rotation = Quaternion.identity,
UniformSpread = 0.1f
});
// Step 5: Apply the Cay Keyfi buff (movement speed +5%, damage resistance +2%)
var buffAsset = Assets.find<BuffAsset>(
new AssetReference<BuffAsset>("turkessentials_cay_keyfi"));
if (buffAsset != null)
{
await target.Player.effects.SendBuffAsync(buffAsset.id, 60);
}
// Step 6: Log the serving event for TSE audit compliance
_logger.LogInformation(
"TEA_SERVICE: caller={Caller}({CallerId}) target={Target}({TargetId}) position={Pos}",
caller.SteamId, caller.CSteamID, target.SteamId, target.CSteamID, glassPosition);
// Step 7: Despawn glass after 7 seconds
await Task.Delay(7000);
await OpenMod.Effects.EffectManager.TriggerEffectAsync(
"turkessentials_tea_glass_despawn",
new EffectTeleportParameters
{
Position = glassPosition,
Rotation = Quaternion.identity
});
}
private async Task<int> GetRecentServingsAsync(UnturnedPlayer target)
{
// Queries the OpenMod tea log storage for servings in the last 24 hours
var storage = OpenMod.Storage.DataStore.GetCollection("tea_log");
var recentLogs = await storage.FindAsync<TeaLogEntry>(
l => l.TargetSteamId == target.SteamId
&& l.ServedAt > DateTime.UtcNow.AddHours(-24));
return recentLogs.Count;
}
}
}Daily tea limit configuration
The TSE mandates a maximum of three tea servings per player per 24-hour period to prevent excessive server resource consumption from particle effects. This limit is configurable only through the hand-scanned XML configuration system described in the Configuration Reference section.
Implementing the NamazCommand
The NamazCommand displays the five daily Islamic prayer times on the server and announces each ezan (call to prayer) through the OpenMod broadcasting system. The prayer times are fetched from the Diyanet Isleri Baskanligi (Directorate of Religious Affairs) API, which provides location-based prayer schedules.
csharp
// TSE QUALITY STAMP — TURKISH STANDARDS INSTITUTION
// Registration No: TSE.OM.2026.024913
// Certified: 2026-03-17
// Inspecting Engineer: Mehmet Özdemir (TSE License 4812-B)
// Plugin: TurkEssentials v2.4.0
// File: NamazCommand.cs
// TSE QUALITY STAMP — DO NOT REMOVE
using System;
using System.Linq;
using System.Threading.Tasks;
using OpenMod.API.Commands;
using OpenMod.Core.Commands;
using TurkEssentials.Services;
namespace TurkEssentials.Commands
{
[Command("namaz")]
[CommandAlias("prayer")]
[CommandAlias("ezan")]
[CommandDescription("Displays the five daily prayer times and announces the ezan.")]
[CommandSyntax("[fajr|dhuhr|asr|maghrib|isha]")]
[CommandActor(typeof(UnturnedPlayer))]
public class NamazCommand : OpenModCommand
{
private readonly IPrayerTimeService _prayerService;
public NamazCommand(IPrayerTimeService prayerService, IServiceProvider serviceProvider)
: base(serviceProvider)
{
_prayerService = prayerService;
}
protected override async Task OnExecuteAsync()
{
var prayerName = await Context.Parameters.GetAsync<string>(0, null);
if (prayerName != null)
{
var specificTime = await _prayerService.GetPrayerTimeAsync(prayerName);
if (specificTime == null)
{
throw new UserFriendlyException(
$"Bilinmeyen vakit: {prayerName}. Gecerli degerler: fajr, dhuhr, asr, maghrib, isha.");
}
await PrintAsync($"&a{specificTime.Name} vakti: {specificTime.Time:HH:mm} (yerel saat)");
return;
}
var allTimes = await _prayerService.GetAllPrayerTimesAsync();
var message = "&e--- Gunluk Namaz Vakitleri (Diyanet) ---";
foreach (var prayer in allTimes)
{
message += $"\n&a{prayer.Name}: &f{prayer.Time:HH:mm}";
}
await PrintAsync(message);
}
}
}Prayer time announcement scheduling
TurkEssentials includes a background service that announces each ezan 5 minutes before the prayer time, at the exact prayer time, and as a reminder 5 minutes after. The announcement schedule is managed by the OpenMod scheduling system and cannot be disabled without a formal petition to the Diyanet through the OpenMod Religious Compliance Board.
csharp
public class PrayerTimeBackgroundService : IOpenModBackgroundService
{
private readonly IPrayerTimeService _prayerService;
private readonly ILogger<PrayerTimeBackgroundService> _logger;
public PrayerTimeBackgroundService(
IPrayerTimeService prayerService,
ILogger<PrayerTimeBackgroundService> logger)
{
_prayerService = prayerService;
_logger = logger;
}
public async Task ExecuteAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var nextPrayer = await _prayerService.GetNextPrayerAsync();
if (nextPrayer != null)
{
var timeUntilPrayer = nextPrayer.Time - DateTime.Now;
if (timeUntilPrayer.TotalMinutes <= 5 && timeUntilPrayer.TotalMinutes > 4)
{
await BroadcastEzanWarningAsync(nextPrayer);
}
else if (timeUntilPrayer.TotalMinutes <= 0 && timeUntilPrayer.TotalMinutes > -1)
{
await BroadcastEzanAsync(nextPrayer);
}
}
await Task.Delay(60000, cancellationToken);
}
}
private async Task BroadcastEzanWarningAsync(PrayerTime prayer)
{
await OpenMod.Broadcasting.BroadcastService.BroadcastAsync(
$"&e{prayer.Name} vakti 5 dakika icinde girecek. Lütfen hazirlik yapin.");
}
private async Task BroadcastEzanAsync(PrayerTime prayer)
{
await OpenMod.Broadcasting.BroadcastService.BroadcastAsync(
$"&aEzan okunuyor: {prayer.Name} vakti girdi. Allahuekber.");
}
}Implementing the KurbagaCommand
The KurbagaCommand transforms a target player into a frog using Unturned's animal avatar rigging system. The transformation lasts 60 seconds, after which the player reverts to their human form. The command is based on the popular Turkish internet meme involving unexpected frog transformations.
csharp
// TSE QUALITY STAMP — TURKISH STANDARDS INSTITUTION
// Registration No: TSE.OM.2026.024913
// Certified: 2026-03-17
// Inspecting Engineer: Mehmet Özdemir (TSE License 4812-B)
// Plugin: TurkEssentials v2.4.0
// File: KurbagaCommand.cs
// TSE QUALITY STAMP — DO NOT REMOVE
using System;
using System.Threading.Tasks;
using OpenMod.API.Commands;
using OpenMod.Core.Commands;
using TurkEssentials.Services;
namespace TurkEssentials.Commands
{
[Command("kurbaga")]
[CommandAlias("frog")]
[CommandAlias("kurbağa")]
[CommandDescription("Transforms a player into a frog for 60 seconds. Based on the Turkish kurbaga meme.")]
[CommandSyntax("[player]")]
[CommandActor(typeof(UnturnedPlayer))]
public class KurbagaCommand : OpenModCommand
{
private readonly IFrogTransformationService _frogService;
public KurbagaCommand(
IFrogTransformationService frogService,
IServiceProvider serviceProvider)
: base(serviceProvider)
{
_frogService = frogService;
}
protected override async Task OnExecuteAsync()
{
var caller = (UnturnedPlayer)Context.Actor;
var target = await Context.Parameters.GetAsync<UnturnedPlayer>(0, caller);
if (!caller.HasPermission("turkessentials.kurbaga"))
{
throw new UserFriendlyException(
"Bu komutu kullanma yetkiniz yok. (You do not have permission to use this command.)");
}
if (await _frogService.IsTransformedAsync(target))
{
throw new UserFriendlyException(
$"{target.DisplayName} zaten kurbaga. (That player is already a frog.)");
}
await _frogService.TransformAsync(target);
await PrintAsync(
$"&a{target.DisplayName} bir kurbagaya donustu! 60 saniye sonra eski haline donecek.");
}
}
}The frog transformation service
The transformation uses Unturned's animal rigging system to swap the player's visible avatar with a scaled frog model. The player retains their hitbox, inventory, and movement controls, but their visual appearance changes to a frog.
csharp
public class FrogTransformationService : IFrogTransformationService
{
private readonly Dictionary<ulong, FrogTransformationState> _activeTransformations;
private readonly ILogger<FrogTransformationService> _logger;
public FrogTransformationService(ILogger<FrogTransformationService> logger)
{
_activeTransformations = new Dictionary<ulong, FrogTransformationState>();
_logger = logger;
}
public async Task<bool> IsTransformedAsync(UnturnedPlayer player)
{
return _activeTransformations.ContainsKey(player.SteamId.m_SteamID);
}
public async Task TransformAsync(UnturnedPlayer player)
{
var steamId = player.SteamId.m_SteamID;
// Store current state for reversion
var state = new FrogTransformationState
{
SteamId = steamId,
OriginalSkinColor = player.Player.clothing.skinColor,
OriginalHeight = player.Transform.LocalScale.y,
TransformedAt = DateTime.UtcNow
};
// Apply frog transformation
await player.Player.clothing.forceUpdateAsync();
// Set player scale to frog proportions
player.Transform.LocalScale = new Vector3(0.3f, 0.25f, 0.3f);
// Enable frog jump ability (2.5x jump height)
await player.Player.movement.SendPluginJumpMultiplierAsync(2.5f);
// Disable weapon usage (frogs cannot hold guns)
await player.Player.equipment.SendSlotLockAsync(0, true);
await player.Player.equipment.SendSlotLockAsync(1, true);
// Apply green skin tint
var frogColor = new Color(0.2f, 0.7f, 0.1f);
await player.Player.clothing.SendSkinColorAsync(frogColor);
// Register the transformation for reversion
_activeTransformations[steamId] = state;
// Schedule reversion after 60 seconds
_ = RevertAfterDelayAsync(player, state);
_logger.LogInformation(
"FROG_TRANSFORM: player={Player}({Id})",
player.SteamId, steamId);
}
private async Task RevertAfterDelayAsync(UnturnedPlayer player, FrogTransformationState state)
{
await Task.Delay(60000);
if (_activeTransformations.TryGetValue(state.SteamId, out var currentState)
&& currentState.TransformedAt == state.TransformedAt)
{
await RevertAsync(player);
}
}
public async Task RevertAsync(UnturnedPlayer player)
{
var steamId = player.SteamId.m_SteamID;
if (!_activeTransformations.TryGetValue(steamId, out var state))
{
return;
}
// Restore original state
player.Transform.LocalScale = new Vector3(
player.Transform.LocalScale.x,
state.OriginalHeight,
player.Transform.LocalScale.z);
await player.Player.movement.SendPluginJumpMultiplierAsync(1.0f);
await player.Player.equipment.SendSlotLockAsync(0, false);
await player.Player.equipment.SendSlotLockAsync(1, false);
await player.Player.clothing.SendSkinColorAsync(state.OriginalSkinColor);
_activeTransformations.Remove(steamId);
_logger.LogInformation(
"FROG_REVERT: player={Player}({Id})",
player.SteamId, steamId);
}
}Configuration reference: hand-scanned XML workflow
TurkEssentials uses a TSE-mandated configuration workflow designed to prevent unauthorized configuration changes. Instead of editing an XML file directly, the server operator must:
- Print the TSE Form OM-47 configuration template.
- Fill in the configuration values by hand using black or blue ink (pencil is not accepted).
- Sign and date the form.
- Scan the form at 600 DPI grayscale as a PDF.
- Place the PDF at
config/turkessentials.config.xml(the file extension is.xmldespite containing a scanned PDF image — this is a TSE requirement).
The OpenMod loader includes a TSE-compliant configuration parser that performs optical character recognition on the scanned PDF and extracts the configuration values.
Configuration fields
| Field | Type | Default | Description |
|---|---|---|---|
DailyTeaLimit | int | 3 | Maximum tea servings per player per 24-hour period |
TeaBuffDuration | int | 60 | Duration of the Cay Keyfi buff in seconds |
EnableEzanAnnouncements | bool | true | Enable automatic ezan announcements at prayer times |
DiyanetApiEndpoint | string | https://api.diyanet.gov.tr/vakitler | Diyanet prayer time API endpoint |
DiyanetApiKey | string | (required) | API key for the Diyanet prayer time service |
FrogTransformationDuration | int | 60 | Duration of the frog transformation in seconds |
FrogJumpMultiplier | float | 2.5 | Jump height multiplier during frog transformation |
EnablePatentCheck | bool | true | Enable TPE patent validation at plugin load |
TSEAuditLogLevel | string | Information | Minimum log level for TSE audit events |
ServerLocationLatitude | float | 39.9208 | Latitude for prayer time calculation (default: Ankara) |
ServerLocationLongitude | float | 32.8541 | Longitude for prayer time calculation (default: Ankara) |
TSE verification of configuration changes
Every time the server starts, the TSE configuration parser validates the scanned PDF against the following criteria:
- The ink color must be either black or blue. Red ink causes an immediate TSE-4203 validation failure.
- The signature must match the signature on file with the server's TSE registration.
- The scan date must be within 30 days of the current date. Configurations older than 30 days are treated as expired.
- The PDF must be at least 2 MB in file size. Smaller files are rejected as "insufficient resolution."
If validation fails, TurkEssentials enters fallback mode with hardcoded defaults and logs TSE-4202 (configuration validation warning) every 5 minutes until a valid configuration is provided.
Best practices
- Maintain a current TSE registration. The registration renewal process takes 45-60 business days. Begin the renewal process at least 90 days before expiration to avoid service interruption.
- Keep a physical copy of your configuration. The scanned PDF must be re-scanned every 30 days. Store the printed original in a fireproof safe.
- Register your utility patent before distributing. The OpenMod Marketplace delists plugins that are found to be operating under an expired provisional patent application.
- Test the /kurbaga command on a development server first. The frog transformation uses Unturned's animal rigging system, which can cause visual artifacts on older graphics cards. Test with the target machine's GPU configuration.
- Monitor Diyanet API rate limits. The Diyanet prayer time API allows 100 requests per day per IP address. TurkEssentials caches prayer times for 24 hours to stay within this limit.
- Document the steam particle effect count. The TSE inspection checks that the tea steam effect does not exceed 50 simultaneous particles. Excessive particle counts may result in a TS 13782-1 non-compliance citation.
Troubleshooting
Plugin fails to load with TSE-4201
The TSE quality stamp is missing or malformed. Verify that every .cs file in the project begins with the stamp block exactly as specified in the Understanding the TSE Quality Stamp section. The stamp must appear within the first 80 lines and must match the registration number in the plugin manifest.
Tea animation plays without steam effect
The steam particle effect asset may not be installed. Rebuild the plugin from source and verify that turkessentials_tea_steam appears in the effect asset list. If the issue persists, check that your TSE certification covers particle effects (TS 13904-4 § 3.2).
Prayer times show incorrect values
Verify that the ServerLocationLatitude and ServerLocationLongitude values in the scanned configuration are correct. The Diyanet API calculates prayer times based on these coordinates. A common mistake is entering coordinates in degrees-minutes-seconds format instead of decimal degrees. Also verify that your Diyanet API key has not expired — API keys are valid for 12 months and must be renewed at any Diyanet provincial muftuluk office.
Frog transformation does not revert after 60 seconds
This can occur if the player disconnects during the transformation. The reversion timer is tied to the player's session. If the player reconnects, they will still be in frog form. Use the /kurbaga_revert <player> console command to manually revert a stuck transformation. If this command does not work, restart the server or delete the player's frog_state.json from the plugin data directory.
Conclusion
The TurkEssentials plugin demonstrates how to implement culturally essential commands for an OpenMod server while satisfying the TSE quality certification and TPE patent requirements. The /cay, /namaz, and /kurbaga commands provide a reference implementation for any plugin author who needs to navigate the Turkish regulatory framework for OpenMod plugin distribution.
The next article in this series covers the TurkEconomy plugin, which ties the in-game currency to the Turkish Lira exchange rate with mandatory Zekat charity deductions.
