diff --git a/src/GameLogic/CastleSiege/CastleSiegeMapAccess.cs b/src/GameLogic/CastleSiege/CastleSiegeMapAccess.cs new file mode 100644 index 0000000..bd0359e --- /dev/null +++ b/src/GameLogic/CastleSiege/CastleSiegeMapAccess.cs @@ -0,0 +1,75 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameLogic.CastleSiege; + +using MUnique.OpenMU.DataModel.Entities; +using MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks; +using MUnique.OpenMU.Interfaces; + +/// +/// ADAMU-CUSTOM (Castle Siege P4 — ownership reward): gates entry to the castle-exclusive hunting map +/// (Land of Trials, map 31). Only members of the current castle-owning guild may enter; while the castle +/// has no owner the map stays sealed. Game masters always pass (admin/testing). +/// +public static class CastleSiegeMapAccess +{ + /// The castle-exclusive hunting map number (Land of Trials). + public const short CastleHuntingMapNumber = 31; + + /// + /// Checks whether the player may enter the given map. Only the castle hunting map (31) is restricted; + /// every other map is always allowed. + /// + /// The player attempting to enter. + /// The target map number. + /// Whether entry is allowed and, when not, a human-readable reason to show the player. + public static async ValueTask<(bool Allowed, string? Error)> CanEnterMapAsync(Player player, short mapNumber) + { + if (mapNumber != CastleHuntingMapNumber) + { + return (true, null); + } + + // Game masters may always enter (admin/testing). + if (player.SelectedCharacter?.CharacterStatus >= CharacterStatus.GameMaster) + { + return (true, null); + } + + var context = CastleSiegeEventPlugIn.TryGetContext(player.GameContext); + var owner = context?.OwnerGuildName; + if (string.IsNullOrEmpty(owner)) + { + return (false, "The castle hunting ground is sealed until a guild owns the castle."); + } + + var guildName = await GetGuildNameAsync(player).ConfigureAwait(false); + if (guildName is not null && string.Equals(guildName, owner, StringComparison.Ordinal)) + { + return (true, null); + } + + return (false, $"Only members of the castle-owning guild '{owner}' may enter this hunting ground."); + } + + private static async ValueTask GetGuildNameAsync(Player player) + { + if (player.GuildStatus is not { } guildStatus) + { + return null; + } + + if (player.GameContext is IGameServerContext serverContext) + { + var guild = await serverContext.GuildServer.GetGuildAsync(guildStatus.GuildId).ConfigureAwait(false); + if (guild?.Name is { Length: > 0 } name) + { + return name; + } + } + + return guildStatus.GuildId.ToString(); + } +} diff --git a/src/GameLogic/PlayerActions/WarpAction.cs b/src/GameLogic/PlayerActions/WarpAction.cs index 7973e35..5e0f988 100644 --- a/src/GameLogic/PlayerActions/WarpAction.cs +++ b/src/GameLogic/PlayerActions/WarpAction.cs @@ -21,6 +21,15 @@ public class WarpAction /// The warp information. public async ValueTask WarpToAsync(Player player, WarpInfo warpInfo) { + // ADAMU-CUSTOM: Castle Siege hunting-map access (Land of Trials, owner guild only). + var (allowed, csError) = await CastleSiege.CastleSiegeMapAccess + .CanEnterMapAsync(player, warpInfo.Gate?.Map?.Number ?? 0).ConfigureAwait(false); + if (!allowed) + { + await player.ShowBlueMessageAsync(csError!).ConfigureAwait(false); + return; + } + if (this.CheckRequirements(player, warpInfo, out var errorMessage)) { await player.WarpToAsync(warpInfo.Gate!).ConfigureAwait(false); diff --git a/src/GameLogic/PlayerActions/WarpGateAction.cs b/src/GameLogic/PlayerActions/WarpGateAction.cs index f073c4c..31ad9e0 100644 --- a/src/GameLogic/PlayerActions/WarpGateAction.cs +++ b/src/GameLogic/PlayerActions/WarpGateAction.cs @@ -55,6 +55,15 @@ public class WarpGateAction return false; } + // ADAMU-CUSTOM: Castle Siege hunting-map access (Land of Trials, owner guild only). + var (csAllowed, csError) = await CastleSiege.CastleSiegeMapAccess + .CanEnterMapAsync(player, enterGate.TargetGate.Map.Number).ConfigureAwait(false); + if (!csAllowed) + { + await player.ShowBlueMessageAsync(csError!).ConfigureAwait(false); + return false; + } + var currentPosition = player.IsWalking ? player.WalkTarget : player.Position; var inaccuracy = player.GameContext.Configuration.InfoRange; if (player.CurrentMap!.Definition.EnterGates.Contains(enterGate)