chat zone config, add more default zones

This commit is contained in:
azyges
2025-12-14 15:55:32 +09:00
parent 03ba9493fc
commit 989d079601
5 changed files with 297 additions and 4 deletions

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace LightlessSyncServer.Configuration;
public sealed class ChatZoneOverridesOptions
{
public List<ChatZoneOverride>? Zones { get; set; }
}
public sealed class ChatZoneOverride
{
public string Key { get; set; } = string.Empty;
public string? DisplayName { get; set; }
public List<string>? TerritoryNames { get; set; }
public List<ushort>? TerritoryIds { get; set; }
}

View File

@@ -61,7 +61,160 @@ internal static class ChatZoneDefinitions
}, },
TerritoryIds: TerritoryRegistry.GetIds( TerritoryIds: TerritoryRegistry.GetIds(
"Ul'dah - Steps of Nald", "Ul'dah - Steps of Nald",
"Ul'dah - Steps of Thal")) "Ul'dah - Steps of Thal")),
new ZoneChannelDefinition(
Key: "ishgard",
DisplayName: "Ishgard",
Descriptor: new ChatChannelDescriptor
{
Type = ChatChannelType.Zone,
WorldId = 0,
ZoneId = 0,
CustomKey = "ishgard"
},
TerritoryNames: new[]
{
"Foundation",
"The Pillars"
},
TerritoryIds: TerritoryRegistry.GetIds(
"Foundation",
"The Pillars")),
new ZoneChannelDefinition(
Key: "kugane",
DisplayName: "Kugane",
Descriptor: new ChatChannelDescriptor
{
Type = ChatChannelType.Zone,
WorldId = 0,
ZoneId = 0,
CustomKey = "kugane"
},
TerritoryNames: new[]
{
"Kugane"
},
TerritoryIds: TerritoryRegistry.GetIds("Kugane")),
new ZoneChannelDefinition(
Key: "crystarium",
DisplayName: "The Crystarium",
Descriptor: new ChatChannelDescriptor
{
Type = ChatChannelType.Zone,
WorldId = 0,
ZoneId = 0,
CustomKey = "crystarium"
},
TerritoryNames: new[]
{
"The Crystarium"
},
TerritoryIds: TerritoryRegistry.GetIds("The Crystarium")),
new ZoneChannelDefinition(
Key: "oldsharlayan",
DisplayName: "Old Sharlayan",
Descriptor: new ChatChannelDescriptor
{
Type = ChatChannelType.Zone,
WorldId = 0,
ZoneId = 0,
CustomKey = "oldsharlayan"
},
TerritoryNames: new[]
{
"Old Sharlayan"
},
TerritoryIds: TerritoryRegistry.GetIds("Old Sharlayan")),
new ZoneChannelDefinition(
Key: "tuliyollal",
DisplayName: "Tuliyollal",
Descriptor: new ChatChannelDescriptor
{
Type = ChatChannelType.Zone,
WorldId = 0,
ZoneId = 0,
CustomKey = "tuliyollal"
},
TerritoryNames: new[]
{
"Tuliyollal"
},
TerritoryIds: TerritoryRegistry.GetIds("Tuliyollal")),
new ZoneChannelDefinition(
Key: "eulmore",
DisplayName: "Eulmore",
Descriptor: new ChatChannelDescriptor
{
Type = ChatChannelType.Zone,
WorldId = 0,
ZoneId = 0,
CustomKey = "eulmore"
},
TerritoryNames: new[]
{
"Eulmore"
},
TerritoryIds: TerritoryRegistry.GetIds("Eulmore")),
new ZoneChannelDefinition(
Key: "idyllshire",
DisplayName: "Idyllshire",
Descriptor: new ChatChannelDescriptor
{
Type = ChatChannelType.Zone,
WorldId = 0,
ZoneId = 0,
CustomKey = "idyllshire"
},
TerritoryNames: new[]
{
"Idyllshire"
},
TerritoryIds: TerritoryRegistry.GetIds("Idyllshire")),
new ZoneChannelDefinition(
Key: "rhalgrsreach",
DisplayName: "Rhalgr's Reach",
Descriptor: new ChatChannelDescriptor
{
Type = ChatChannelType.Zone,
WorldId = 0,
ZoneId = 0,
CustomKey = "rhalgrsreach"
},
TerritoryNames: new[]
{
"Rhalgr's Reach"
},
TerritoryIds: TerritoryRegistry.GetIds("Rhalgr's Reach")),
new ZoneChannelDefinition(
Key: "radzathan",
DisplayName: "Radz-at-Han",
Descriptor: new ChatChannelDescriptor
{
Type = ChatChannelType.Zone,
WorldId = 0,
ZoneId = 0,
CustomKey = "radzathan"
},
TerritoryNames: new[]
{
"Radz-at-Han"
},
TerritoryIds: TerritoryRegistry.GetIds("Radz-at-Han")),
new ZoneChannelDefinition(
Key: "solutionnine",
DisplayName: "Solution Nine",
Descriptor: new ChatChannelDescriptor
{
Type = ChatChannelType.Zone,
WorldId = 0,
ZoneId = 0,
CustomKey = "solutionnine"
},
TerritoryNames: new[]
{
"Solution Nine"
},
TerritoryIds: TerritoryRegistry.GetIds("Solution Nine"))
}; };
} }

View File

@@ -1,7 +1,9 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using LightlessSync.API.Data; using LightlessSync.API.Data;
using LightlessSync.API.Dto.Chat; using LightlessSync.API.Dto.Chat;
using LightlessSyncServer.Configuration;
using LightlessSyncServer.Models; using LightlessSyncServer.Models;
using Microsoft.Extensions.Options;
namespace LightlessSyncServer.Services; namespace LightlessSyncServer.Services;
@@ -21,14 +23,132 @@ public sealed class ChatChannelService : IDisposable
private static readonly TimeSpan InactiveParticipantCleanupInterval = TimeSpan.FromMinutes(1); private static readonly TimeSpan InactiveParticipantCleanupInterval = TimeSpan.FromMinutes(1);
private readonly Timer _inactiveParticipantCleanupTimer; private readonly Timer _inactiveParticipantCleanupTimer;
public ChatChannelService(ILogger<ChatChannelService> logger) public ChatChannelService(ILogger<ChatChannelService> logger, IOptions<ChatZoneOverridesOptions>? zoneOverrides = null)
{ {
_logger = logger; _logger = logger;
_zoneDefinitions = ChatZoneDefinitions.Defaults _zoneDefinitions = BuildZoneDefinitions(zoneOverrides?.Value);
.ToDictionary(definition => definition.Key, StringComparer.OrdinalIgnoreCase);
_inactiveParticipantCleanupTimer = new Timer(_ => CleanupExpiredInactiveParticipants(), null, InactiveParticipantCleanupInterval, InactiveParticipantCleanupInterval); _inactiveParticipantCleanupTimer = new Timer(_ => CleanupExpiredInactiveParticipants(), null, InactiveParticipantCleanupInterval, InactiveParticipantCleanupInterval);
} }
private Dictionary<string, ZoneChannelDefinition> BuildZoneDefinitions(ChatZoneOverridesOptions? overrides)
{
var definitions = ChatZoneDefinitions.Defaults
.ToDictionary(definition => definition.Key, StringComparer.OrdinalIgnoreCase);
if (overrides?.Zones is null || overrides.Zones.Count == 0)
{
return definitions;
}
foreach (var entry in overrides.Zones)
{
if (entry is null)
{
continue;
}
if (!TryCreateZoneDefinition(entry, out var definition))
{
continue;
}
definitions[definition.Key] = definition;
}
return definitions;
}
private bool TryCreateZoneDefinition(ChatZoneOverride entry, out ZoneChannelDefinition definition)
{
definition = default;
var key = NormalizeZoneKey(entry.Key);
if (string.IsNullOrEmpty(key))
{
_logger.LogWarning("Skipped chat zone override with missing key.");
return false;
}
var territoryIds = new HashSet<ushort>();
if (entry.TerritoryIds is not null)
{
foreach (var candidate in entry.TerritoryIds)
{
if (candidate > 0)
{
territoryIds.Add(candidate);
}
}
}
var territoryNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (entry.TerritoryNames is not null)
{
foreach (var name in entry.TerritoryNames)
{
if (string.IsNullOrWhiteSpace(name))
continue;
var trimmed = name.Trim();
territoryNames.Add(trimmed);
if (TerritoryRegistry.TryGetIds(trimmed, out var ids))
{
territoryIds.UnionWith(ids);
}
else
{
_logger.LogWarning("Chat zone override {Zone} references unknown territory '{Territory}'.", key, trimmed);
}
}
}
if (territoryIds.Count == 0)
{
_logger.LogWarning("Skipped chat zone override for {Zone}: no territory IDs resolved.", key);
return false;
}
if (territoryNames.Count == 0)
{
foreach (var territoryId in territoryIds)
{
if (TerritoryRegistry.ById.TryGetValue(territoryId, out var territory))
{
territoryNames.Add(territory.Name);
}
}
}
if (territoryNames.Count == 0)
{
territoryNames.Add("Territory");
}
var descriptor = new ChatChannelDescriptor
{
Type = ChatChannelType.Zone,
WorldId = 0,
ZoneId = 0,
CustomKey = key
};
var displayName = string.IsNullOrWhiteSpace(entry.DisplayName)
? key
: entry.DisplayName.Trim();
definition = new ZoneChannelDefinition(
key,
displayName,
descriptor,
territoryNames.ToArray(),
territoryIds);
return true;
}
private static string NormalizeZoneKey(string? value) =>
string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
public IReadOnlyList<ZoneChatChannelInfoDto> GetZoneChannelInfos() => public IReadOnlyList<ZoneChatChannelInfoDto> GetZoneChannelInfos() =>
_zoneDefinitions.Values _zoneDefinitions.Values
.Select(definition => new ZoneChatChannelInfoDto( .Select(definition => new ZoneChatChannelInfoDto(

View File

@@ -93,6 +93,7 @@ public class Startup
services.Configure<ServerConfiguration>(Configuration.GetRequiredSection("LightlessSync")); services.Configure<ServerConfiguration>(Configuration.GetRequiredSection("LightlessSync"));
services.Configure<LightlessConfigurationBase>(Configuration.GetRequiredSection("LightlessSync")); services.Configure<LightlessConfigurationBase>(Configuration.GetRequiredSection("LightlessSync"));
services.Configure<BroadcastOptions>(Configuration.GetSection("Broadcast")); services.Configure<BroadcastOptions>(Configuration.GetSection("Broadcast"));
services.Configure<ChatZoneOverridesOptions>(Configuration.GetSection("ChatZoneOverrides"));
services.AddSingleton<IBroadcastConfiguration, BroadcastConfiguration>(); services.AddSingleton<IBroadcastConfiguration, BroadcastConfiguration>();
services.AddSingleton<ServerTokenGenerator>(); services.AddSingleton<ServerTokenGenerator>();

View File

@@ -41,6 +41,9 @@
"PairRequestRateLimit": 5, "PairRequestRateLimit": 5,
"PairRequestRateWindow": 60 "PairRequestRateWindow": 60
}, },
"ChatZoneOverrides": {
"Zones": []
},
"AllowedHosts": "*", "AllowedHosts": "*",
"Kestrel": { "Kestrel": {
"Endpoints": { "Endpoints": {