From 51d2e998fcdf31f7e09826cb8c5df33988b814e4 Mon Sep 17 00:00:00 2001 From: Robert Dickey Date: Fri, 11 Sep 2026 19:26:06 -0500 Subject: [PATCH] fix: respect active region MountsAllowed for players (#2630) A player can mount a horse or validate an ethereal mount inside a BaseRegion that overrides MountsAllowed to false because CheckMountAllowed never consults that property. This adds the missing check in the shared player permission path, using existing localized message 1042317. --- .../Tests/Mobiles/MountRegionTests.cs | 96 +++++++++++++++++++ .../Mobiles/Animals/Mounts/BaseMount.cs | 7 ++ 2 files changed, 103 insertions(+) create mode 100644 Projects/UOContent.Tests/Tests/Mobiles/MountRegionTests.cs diff --git a/Projects/UOContent.Tests/Tests/Mobiles/MountRegionTests.cs b/Projects/UOContent.Tests/Tests/Mobiles/MountRegionTests.cs new file mode 100644 index 000000000..6369ddda9 --- /dev/null +++ b/Projects/UOContent.Tests/Tests/Mobiles/MountRegionTests.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using Server; +using Server.Mobiles; +using Server.Items; +using Server.Regions; +using Xunit; + +namespace UOContent.Tests.Mobiles; + +[Collection("Sequential UOContent Tests")] +public class MountRegionTests +{ + public static IEnumerable MountCases() + { + foreach (var era in Enum.GetValues()) + { + foreach (var allowed in new[] { false, true }) + { + yield return new object[] { era, allowed, false }; + yield return new object[] { era, allowed, true }; + } + } + } + + [Theory] + [MemberData(nameof(MountCases))] + public void Mounting_RespectsActiveRegion(Expansion era, bool allowed, bool ethereal) + { + var previous = Core.Expansion; + var region = new MountTestRegion(allowed); + PlayerMobile player = null; + TestHorse horse = null; + EtherealHorse statue = null; + try + { + Core.Expansion = era; + region.Register(); + player = new PlayerMobile(World.NewMobile); + player.DefaultMobileInit(); + player.Race = Race.Human; + player.AddItem(new Backpack()); + player.MoveToWorld(new Point3D(1000, 1000, 0), Map.Felucca); + Assert.Same(region, player.Region); + + if (ethereal) + { + statue = new EtherealHorse { IsRewardItem = false }; + player.Backpack.DropItem(statue); + Assert.Same(player.Backpack, statue.Parent); + Assert.Equal(allowed, statue.Validate(player)); + } + else + { + horse = new TestHorse(); + horse.MoveToWorld(player.Location, player.Map); + horse.SetControlMaster(player); + horse.OnDoubleClick(player); + Assert.Equal(allowed, horse.Rider == player); + Assert.Equal(allowed, player.Mounted); + } + } + finally + { + horse?.Delete(); + statue?.Delete(); + player?.Delete(); + region.Unregister(); + Core.Expansion = previous; + } + } + + private class MountTestRegion : BaseRegion + { + private readonly bool _allowed; + + public MountTestRegion(bool allowed) + : base("MountRegionTest", Map.Felucca, 100, + new Rectangle3D(990, 990, -128, 20, 20, 256)) + { + _allowed = allowed; + } + + public override bool MountsAllowed => _allowed; + } + + // The test fixture does not configure NPCSpeeds. + private class TestHorse : Horse + { + public override void GetSpeeds(out double activeSpeed, out double passiveSpeed) + { + activeSpeed = 0.2; + passiveSpeed = 0.4; + } + } +} diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/BaseMount.cs b/Projects/UOContent/Mobiles/Animals/Mounts/BaseMount.cs index 956f13034..43e7a598c 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/BaseMount.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/BaseMount.cs @@ -3,6 +3,7 @@ using System; using Server.Items; using Server.Misc; using Server.Multis; +using Server.Regions; using Server.Targeting; namespace Server.Mobiles; @@ -286,6 +287,12 @@ public abstract partial class BaseMount : BaseCreature, IMount if (mob is PlayerMobile mobile) { + if (mobile.Region is BaseRegion { MountsAllowed: false }) + { + mobile.SendLocalizedMessage(1042317); // You may not ride at this time + result = false; + } + if (mobile.MountBlockReason != BlockMountType.None) { mobile.SendLocalizedMessage((int)mobile.MountBlockReason);