Files
AdamuSw/src/GameLogic/CastleSiege/CastleSiegeConfiguration.cs
Acentech Dev fbe75d55be
Some checks failed
.NET Core / build (push) Has been cancelled
feat(CS): run the siege on ONE designated server (CastleSiegeServerId)
With multiple game servers each has its own map instances, so the siege ran
independently on all of them - a guild could win uncontested on an empty server's
Valley of Loren. Now the siege (tick/spawn/battle/registration) runs only on the
server whose Id == config.CastleSiegeServerId (AdminPanel-editable). Other servers
skip the siege and just mirror the shared castle owner from config so the hunting-map
gate + castle flag rewards still work everywhere. The Guardsman on non-siege servers
tells players which server to switch to.
2026-07-15 19:10:48 +03:00

194 lines
7.6 KiB
C#

// <copyright file="CastleSiegeConfiguration.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.CastleSiege;
using System.ComponentModel;
using System.Linq;
using System.Text.Json.Serialization;
/// <summary>
/// Configuration for the Castle Siege cycle. Rides on the plugin custom-configuration system (no dedicated
/// database table). A cycle runs: Ownership -> Registration -> Preparation -> Siege(war) -> Settlement, and
/// auto-starts when the current day/time matches <see cref="OpenDays"/> + <see cref="RegistrationOpenTimes"/>.
/// The AdminPanel-friendly properties (OpenDays checkboxes, minute/second durations) are proxies over the
/// runtime fields, which are hidden from the editor to keep the form clean.
/// </summary>
public class CastleSiegeConfiguration
{
/// <summary>
/// Gets or sets the days of week on which registration auto-opens (UTC). None = every day (still needs a
/// time). Editable in the AdminPanel as check boxes; proxies the runtime <see cref="RegistrationOpenDays"/>.
/// </summary>
[JsonIgnore]
public CastleSiegeScheduleDays OpenDays
{
get
{
var days = CastleSiegeScheduleDays.None;
foreach (var day in this.RegistrationOpenDays)
{
days |= (CastleSiegeScheduleDays)(1 << (int)day);
}
return days;
}
set => this.RegistrationOpenDays = Enum.GetValues<DayOfWeek>()
.Where(day => value.HasFlag((CastleSiegeScheduleDays)(1 << (int)day)))
.ToList();
}
/// <summary>
/// Gets or sets the times of day (UTC) at which a new cycle opens registration.
/// Empty = no auto-start (admins start cycles manually via the chat command).
/// </summary>
public IList<TimeOnly> RegistrationOpenTimes { get; set; } = new List<TimeOnly>();
/// <summary>Gets or sets how long the registration period lasts, in minutes (guilds may register).</summary>
[JsonIgnore]
public int RegistrationMinutes
{
get => (int)this.RegistrationDuration.TotalMinutes;
set => this.RegistrationDuration = TimeSpan.FromMinutes(Math.Max(1, value));
}
/// <summary>Gets or sets how long the preparation period lasts, in minutes (between registration and war).</summary>
[JsonIgnore]
public int PreparationMinutes
{
get => (int)this.PreparationDuration.TotalMinutes;
set => this.PreparationDuration = TimeSpan.FromMinutes(Math.Max(0, value));
}
/// <summary>Gets or sets how long the war (siege) period lasts, in minutes.</summary>
[JsonIgnore]
public int SiegeMinutes
{
get => (int)this.SiegeDuration.TotalMinutes;
set => this.SiegeDuration = TimeSpan.FromMinutes(Math.Max(1, value));
}
/// <summary>
/// Gets or sets how long the guild master must hold the Crown to capture the throne, in seconds.
/// The client shows a 60-second countdown, so 60 matches the on-screen timer.
/// </summary>
[JsonIgnore]
public int CrownHoldSeconds
{
get => (int)this.CrownHoldDuration.TotalSeconds;
set => this.CrownHoldDuration = TimeSpan.FromSeconds(Math.Max(1, value));
}
/// <summary>
/// Gets or sets the registration fee (in zen) a guild master must pay to register the guild
/// for the siege. 0 disables the fee.
/// </summary>
public int RegistrationFee { get; set; } = 100000;
/// <summary>
/// Gets or sets the game server (SW) id on which the Castle Siege runs. With multiple game servers each
/// has its own map instances, so the siege must happen on ONE designated server; the others skip it (they
/// only honor the shared castle owner for the rewards). Players must be on this server to take part.
/// </summary>
public byte CastleSiegeServerId { get; set; }
// --- Runtime fields (hidden from the AdminPanel; proxied by the friendly properties above) ---
/// <summary>Gets or sets the days of week registration auto-opens (UTC). Empty = every day.</summary>
[Browsable(false)]
public IList<DayOfWeek> RegistrationOpenDays { get; set; } = new List<DayOfWeek>();
/// <summary>Gets or sets how long the registration period lasts.</summary>
[Browsable(false)]
public TimeSpan RegistrationDuration { get; set; } = TimeSpan.FromMinutes(5);
/// <summary>Gets or sets how long the preparation period lasts.</summary>
[Browsable(false)]
public TimeSpan PreparationDuration { get; set; } = TimeSpan.FromMinutes(2);
/// <summary>Gets or sets how long the war (siege) period lasts.</summary>
[Browsable(false)]
public TimeSpan SiegeDuration { get; set; } = TimeSpan.FromMinutes(10);
/// <summary>Gets or sets how long the guild master must hold the Crown to capture the throne.</summary>
[Browsable(false)]
public TimeSpan CrownHoldDuration { get; set; } = TimeSpan.FromSeconds(60);
// --- Persisted runtime state (hidden from the AdminPanel) ---
// These ride on the plugin's custom-configuration JSON (stored in PostgreSQL), so the castle owner and the
// current cycle survive server restarts. Written by CastleSiegeEventPlugIn; restored on startup.
/// <summary>Gets or sets the persisted castle owner guild name (null = unowned).</summary>
[Browsable(false)]
public string? PersistedOwnerGuildName { get; set; }
/// <summary>Gets or sets the persisted current phase, so the cycle resumes after a restart.</summary>
[Browsable(false)]
public CastleSiegePhase PersistedPhase { get; set; } = CastleSiegePhase.Ownership;
/// <summary>Gets or sets when the persisted phase started (UTC), or null if never persisted.</summary>
[Browsable(false)]
public DateTime? PersistedPhaseStartedUtc { get; set; }
/// <summary>Gets or sets the persisted registered guild names for the current cycle.</summary>
[Browsable(false)]
public IList<string> PersistedRegisteredGuilds { get; set; } = new List<string>();
/// <summary>
/// Returns true if <paramref name="now"/> (UTC) matches a scheduled registration-open day and falls
/// within a 5-second window of a configured open time. When <see cref="RegistrationOpenDays"/> is empty,
/// the day is not restricted (every day). When no times are configured, auto-start is disabled.
/// </summary>
/// <param name="now">The current UTC time.</param>
public bool IsRegistrationOpenTime(DateTime now)
{
if (this.RegistrationOpenTimes.Count == 0)
{
return false;
}
if (this.RegistrationOpenDays.Count > 0 && !this.RegistrationOpenDays.Contains(now.DayOfWeek))
{
return false;
}
var nowTime = TimeOnly.FromDateTime(now);
var earlier = nowTime.Add(TimeSpan.FromSeconds(-5));
return this.RegistrationOpenTimes.Any(p => p.IsBetween(earlier, nowTime));
}
}
/// <summary>
/// The days of week on which the Castle Siege registration auto-opens. A [Flags] enum so the AdminPanel
/// renders it as a set of check boxes.
/// </summary>
[Flags]
public enum CastleSiegeScheduleDays
{
/// <summary>No day (auto-start day-unrestricted; still needs a time).</summary>
None = 0,
/// <summary>Sunday.</summary>
Sunday = 1 << 0,
/// <summary>Monday.</summary>
Monday = 1 << 1,
/// <summary>Tuesday.</summary>
Tuesday = 1 << 2,
/// <summary>Wednesday.</summary>
Wednesday = 1 << 3,
/// <summary>Thursday.</summary>
Thursday = 1 << 4,
/// <summary>Friday.</summary>
Friday = 1 << 5,
/// <summary>Saturday.</summary>
Saturday = 1 << 6,
}