diff --git a/src/GameLogic/CastleSiege/CastleSiegeHuntingPortalTalkPlugIn.cs b/src/GameLogic/CastleSiege/CastleSiegeHuntingPortalTalkPlugIn.cs
new file mode 100644
index 0000000..f736f80
--- /dev/null
+++ b/src/GameLogic/CastleSiege/CastleSiegeHuntingPortalTalkPlugIn.cs
@@ -0,0 +1,59 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.GameLogic.CastleSiege;
+
+using System.Runtime.InteropServices;
+using MUnique.OpenMU.GameLogic.NPC;
+using MUnique.OpenMU.GameLogic.PlugIns;
+using MUnique.OpenMU.GameLogic.Views;
+using MUnique.OpenMU.Interfaces;
+using MUnique.OpenMU.PlugIns;
+
+///
+/// ADAMU-CUSTOM (Castle Siege P4 — ownership reward): the castle Guard (NPC 220) on Valley of Loren is the
+/// portal into the castle-exclusive hunting ground (Land of Trials, map 31). It warps a player there only
+/// when their guild owns the castle (or they are a game master); otherwise it explains why they can't enter.
+///
+[Guid("CA5710A0-7A1B-4C2D-8E3F-000000000220")]
+[PlugIn]
+[Display(Name = "Castle Siege Hunting Portal (Guard)", Description = "Guard NPC 220 on Valley of Loren warps the castle-owning guild into Land of Trials (map 31).")]
+public class CastleSiegeHuntingPortalTalkPlugIn : IPlayerTalkToNpcPlugIn
+{
+ private const short GuardNpcNumber = 220;
+ private const short ValleyOfLorenMapNumber = 30;
+
+ ///
+ public async ValueTask PlayerTalksToNpcAsync(Player player, NonPlayerCharacter npc, NpcTalkEventArgs eventArgs)
+ {
+ // Only the castle Guard on Valley of Loren acts as the hunting-ground portal.
+ if (npc.Definition.Number != GuardNpcNumber
+ || player.CurrentMap?.Definition.Number != ValleyOfLorenMapNumber)
+ {
+ return;
+ }
+
+ eventArgs.HasBeenHandled = true;
+
+ var (allowed, error) = await CastleSiegeMapAccess
+ .CanEnterMapAsync(player, CastleSiegeMapAccess.CastleHuntingMapNumber).ConfigureAwait(false);
+ if (!allowed)
+ {
+ await ShowAsync(player, error!).ConfigureAwait(false);
+ return;
+ }
+
+ var map = await player.GameContext.GetMapAsync((ushort)CastleSiegeMapAccess.CastleHuntingMapNumber).ConfigureAwait(false);
+ if (map?.SafeZoneSpawnGate is not { } gate)
+ {
+ await ShowAsync(player, "The castle hunting ground is not available right now.").ConfigureAwait(false);
+ return;
+ }
+
+ await player.WarpToAsync(gate).ConfigureAwait(false);
+ }
+
+ private static ValueTask ShowAsync(Player player, string text)
+ => player.InvokeViewPlugInAsync(p => p.ShowMessageAsync(text, MessageType.BlueNormal));
+}