Skip to content

Naval Travel

Noxix [ADOD, KOA, ER, SC, IAF]: Major thanks to order_without_power and npc99. and the creators of Calradia Expanded, whom this code comes from.

Chat link

using HarmonyLib;
using SandBox;
using SandBox.View.Map;
using System;
using System.Reflection;
using TaleWorlds.CampaignSystem;
using TaleWorlds.CampaignSystem.Party;
using TaleWorlds.Core;
using TaleWorlds.Engine;
using TaleWorlds.Library;

namespace YOUR_NAMESPACE_HERE
{
    [HarmonyPatch]
    public class Patches
    {
        [HarmonyPrefix]
        [HarmonyPatch(typeof(MapScreen), "StepSounds")]
        private static bool Prefix1(MobileParty party) => Campaign.Current.MapSceneWrapper.GetFaceTerrainType(party.CurrentNavigationFace) != TerrainType.Water;

        [HarmonyPostfix]
        [HarmonyPatch(typeof(MapScene), "DisableUnwalkableNavigationMeshes")]
        public static void Postfix1(MapScene __instance) => __instance.Scene.SetAbilityOfFacesWithId(MapScene.GetNavigationMeshIndexOfTerrainType(TerrainType.Water), true);

        [HarmonyPostfix]
        [HarmonyPatch(typeof(MapScene), "GetHeightAtPoint")]
        public static void Postfix2(MapScene __instance, ref float height) => height = MathF.Max(height, __instance.Scene.GetWaterLevel());

        [HarmonyPostfix]
        [HarmonyPatch(typeof(PartyVisual), "TickMobilePartyVisual")]
        private static void Postfix3(PartyVisual __instance)
        {
            GameEntity strategicEntity = __instance.StrategicEntity;

            if (Campaign.Current.MapSceneWrapper.GetTerrainTypeAtPosition(__instance.Position) == TerrainType.Water && strategicEntity.ChildCount == 0)
            {
                MatrixFrame identity = MatrixFrame.Identity;
                GameEntity gameEntity = GameEntity.CreateEmpty(strategicEntity.Scene, true);
                string metaMeshName = "boat_sail_on"/* (default mesh) */, bannerKey = __instance.PartyBase.LeaderHero?.ClanBanner?.Serialize(), bannerMeshName = "campaign_flag";

                identity.rotation.ApplyScaleLocal(0.25f);// You can change the scale of the mesh.
                gameEntity.SetFrame(ref identity);
                gameEntity.AddMultiMesh(MetaMesh.GetCopy(metaMeshName, true, false), true);

                if (!string.IsNullOrEmpty(bannerKey))
                {
                    try
                    {
                        gameEntity.AddMultiMesh((MetaMesh)AccessTools.Method(typeof(PartyVisual), "GetBannerOfCharacter").Invoke(null, new object[] { new Banner(bannerKey), bannerMeshName }), true);
                    }
                    catch (Exception)
                    {
                        InformationManager.DisplayMessage(new InformationMessage(MethodBase.GetCurrentMethod().DeclaringType.FullName + "." + MethodBase.GetCurrentMethod().Name + ": Error adding banner to ship visual of " + __instance.PartyBase.Name + "!"));
                    }
                }

                strategicEntity.AddChild(gameEntity, false);

                __instance.HumanAgentVisuals?.Reset();
                __instance.MountAgentVisuals?.Reset();
                __instance.CaravanMountAgentVisuals?.Reset();

                AccessTools.Property(typeof(PartyVisual), "HumanAgentVisuals").SetValue(__instance, null);
                AccessTools.Property(typeof(PartyVisual), "MountAgentVisuals").SetValue(__instance, null);
                AccessTools.Property(typeof(PartyVisual), "CaravanMountAgentVisuals").SetValue(__instance, null);
            }
            else if (Campaign.Current.MapSceneWrapper.GetTerrainTypeAtPosition(__instance.Position) != TerrainType.Water && strategicEntity.ChildCount > 0)
            {
                __instance.PartyBase.SetVisualAsDirty();
            }
        }
    }
}

The code above makes all the water passable. If more control is necessary, then it is possible to mark a navmesh tile as '6' - "Fording" and allow to travel over water only on those tiles.

In such case use this code:

   [HarmonyPatch]
    public class Patches
    {
        [HarmonyPrefix]
        [HarmonyPatch(typeof(MapScreen), "StepSounds")]
        private static bool Prefix1(MobileParty party) => Campaign.Current.MapSceneWrapper.GetFaceTerrainType(party.CurrentNavigationFace) != TerrainType.Fording;

        [HarmonyPostfix]
        [HarmonyPatch(typeof(MapScene), "GetHeightAtPoint")]
        public static void Postfix2(MapScene __instance, ref float height) => height = MathF.Max(height, __instance.Scene.GetWaterLevel());

        [HarmonyPostfix]
        [HarmonyPatch(typeof(PartyVisual), "TickMobilePartyVisual")]
        private static void Postfix3(PartyVisual __instance)
        {
            GameEntity strategicEntity = __instance.StrategicEntity;

            if (Campaign.Current.MapSceneWrapper.GetTerrainTypeAtPosition(__instance.Position) == TerrainType.Fording && strategicEntity.ChildCount == 0)
            {
                MatrixFrame identity = MatrixFrame.Identity;
                GameEntity gameEntity = GameEntity.CreateEmpty(strategicEntity.Scene, true);
                string metaMeshName = "boat_sail_on"/* (default mesh) */, bannerKey = __instance.PartyBase.LeaderHero?.ClanBanner?.Serialize(), bannerMeshName = "campaign_flag";

                identity.rotation.ApplyScaleLocal(0.25f);// You can change the scale of the mesh.
                gameEntity.SetFrame(ref identity);
                gameEntity.AddMultiMesh(MetaMesh.GetCopy(metaMeshName, true, false), true);

                if (!string.IsNullOrEmpty(bannerKey))
                {
                    try
                    {
                        gameEntity.AddMultiMesh((MetaMesh)AccessTools.Method(typeof(PartyVisual), "GetBannerOfCharacter").Invoke(null, new object[] { new Banner(bannerKey), bannerMeshName }), true);
                    }
                    catch (Exception)
                    {
                        InformationManager.DisplayMessage(new InformationMessage(MethodBase.GetCurrentMethod().DeclaringType.FullName + "." + MethodBase.GetCurrentMethod().Name + ": Error adding banner to ship visual of " + __instance.PartyBase.Name + "!"));
                    }
                }

                strategicEntity.AddChild(gameEntity, false);

                __instance.HumanAgentVisuals?.Reset();
                __instance.MountAgentVisuals?.Reset();
                __instance.CaravanMountAgentVisuals?.Reset();

                AccessTools.Property(typeof(PartyVisual), "HumanAgentVisuals").SetValue(__instance, null);
                AccessTools.Property(typeof(PartyVisual), "MountAgentVisuals").SetValue(__instance, null);
                AccessTools.Property(typeof(PartyVisual), "CaravanMountAgentVisuals").SetValue(__instance, null);
            }
            else if (Campaign.Current.MapSceneWrapper.GetTerrainTypeAtPosition(__instance.Position) != TerrainType.Fording && strategicEntity.ChildCount > 0)
            {
                __instance.PartyBase.SetVisualAsDirty();
            }
        }
    }