Skip to content

Commit

Permalink
Add mob pathing for roaming (#460)
Browse files Browse the repository at this point in the history
Required are nav files which can be generated by uthgard-opensource/builnav
  • Loading branch information
NetDwarf authored Jun 29, 2023
2 parents 9761452 + ead44d9 commit 86eb0b3
Show file tree
Hide file tree
Showing 38 changed files with 10,779 additions and 5 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/create_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,36 @@ jobs:
uses: actions/setup-dotnet@v3
with:
dotnet-version: '6.0.x'
- name: Setup MinGW-w64
uses: egor-tensin/setup-mingw@v2
with:
platform: x64
- name: Build
run: |
export DOTNET_CLI_TELEMETRY_OPTOUT=1
assembly_version="${{ needs.get_release_info.outputs.version }}"
dotnet build --configuration "${{ matrix.build_target}}" -p:Version="$assembly_version" --verbosity normal "Dawn of Light.sln"
echo "dotnet DOLServer.dll" > "${{ matrix.build_target }}/DOLServer.bat"
- name: Build DOL Detour (Linux x64)
run: |
mkdir Pathing/Detour/build
cd Pathing/Detour/build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ ..
make
cp libdol_detour.so ../../../${{ matrix.build_target }}/
- name: Build DOL Detour (Windows x64)
run: |
mkdir Pathing/Detour/build_win64
cd Pathing/Detour/build_win64
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++-posix -DCMAKE_SHARED_LINKER_FLAGS="-static" ..
make
cp libdol_detour.dll ../../../${{ matrix.build_target }}/dol_detour.dll
- name: Test Build
run: |
dotnet test --verbosity normal --filter "DOL.UnitTests&TestCategory!=Explicit" ./build/Tests/${{ matrix.build_target }}/lib/Tests.dll
dotnet test --verbosity normal --filter "DOL.Integration&TestCategory!=Explicit" ./build/Tests/${{ matrix.build_target }}/lib/Tests.dll
- name: Add DOLConfig
if: ${{ github.repository_owner == 'Dawn-of-Light' }}
run: |
dolconfig_location="$(dirname $(git remote get-url origin))/DOLConfig/releases/latest/download/DOLConfig_${{ matrix.build_target }}.zip"
wget "$dolconfig_location" -O DOLConfig.zip
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
/old/build
/old/Debug
/old/Release
/Pathing/Detour/build
**/*.csproj.user
5 changes: 5 additions & 0 deletions GameServer/GameServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,11 @@ public override bool Start()
if (!InitComponent(WorldMgr.EarlyInit(out regionsData), "World Manager PreInitialization"))
return false;

//---------------------------------------------------------------
//Try to initialize the Pathing Manager
if (!InitComponent(PathingMgr.Init(), "Pathing Manager Initialization"))
return false;

//---------------------------------------------------------------
//Try to initialize the script components
if (!InitComponent(StartScriptComponents(), "Script components"))
Expand Down
11 changes: 10 additions & 1 deletion GameServer/ai/brain/StandardMobBrain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
using DOL.GS.Keeps;
using DOL.Language;
using log4net;
using System.Numerics;

namespace DOL.AI.Brain
{
Expand Down Expand Up @@ -138,7 +139,7 @@ public override void Think()
}
else
{
Body.WalkTo(target, 50);
Body.PathTo(target, 50);
}

Body.FireAmbientSentence(GameNPC.eAmbientTrigger.roaming);
Expand Down Expand Up @@ -1552,6 +1553,14 @@ defaut roaming range is defined in CanRandomWalk method

public virtual IPoint3D CalcRandomWalkTarget()
{
if (PathCalculator.IsSupported(Body))
{
int radius = Body.RoamingRange > 0 ? Body.RoamingRange : 500;
var target = PathingMgr.Instance.GetRandomPointAsync(Body.CurrentZone, new Vector3(Body.X, Body.Y, Body.Z), radius);
if (target.HasValue)
return new Point3D(target.Value.X, target.Value.Y, target.Value.Z);
}

int maxRoamingRadius = Body.CurrentRegion.IsDungeon ? 5 : 500;

if (Body.RoamingRange > 0)
Expand Down
1 change: 1 addition & 0 deletions GameServer/commands/gmcommands/GMinfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,7 @@ public void OnCommand(GameClient client, string[] args)
info.Add(" Zone Height: "+ client.Player.CurrentZone.Height);
info.Add(" Zone DivingEnabled: " + client.Player.CurrentZone.IsDivingEnabled);
info.Add(" Zone Waterlevel: " + client.Player.CurrentZone.Waterlevel);
info.Add(" Zone Pathing: " + (PathingMgr.Instance.HasNavmesh(client.Player.CurrentZone) ? "enabled" : "disabled"));
info.Add(" ");
info.Add(" Region Name: "+ client.Player.CurrentRegion.Name);
info.Add(" Region Description: " + client.Player.CurrentRegion.Description);
Expand Down
101 changes: 97 additions & 4 deletions GameServer/gameobjects/GameNPC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
using DOL.Language;
using DOL.GS.ServerProperties;
using DOL.GS.Finance;
using System.Threading.Tasks;
using System.Numerics;

namespace DOL.GS
{
Expand Down Expand Up @@ -1367,13 +1369,16 @@ public uint LastVisibleToPlayersTickCount
/// </summary>
protected class ArriveAtTargetAction : RegionAction
{
private Action<GameNPC> m_goToNodeCallback;

/// <summary>
/// Constructs a new ArriveAtTargetAction
/// </summary>
/// <param name="actionSource">The action source</param>
public ArriveAtTargetAction(GameNPC actionSource)
public ArriveAtTargetAction(GameNPC actionSource, Action<GameNPC> goToNodeCallback = null)
: base(actionSource)
{
m_goToNodeCallback = goToNodeCallback;
}

/// <summary>
Expand All @@ -1384,6 +1389,11 @@ public ArriveAtTargetAction(GameNPC actionSource)
protected override void OnTick()
{
GameNPC npc = (GameNPC)m_actionSource;
if (m_goToNodeCallback != null)
{
m_goToNodeCallback(npc);
return;
}

bool arriveAtSpawnPoint = npc.IsReturningToSpawnPoint;

Expand Down Expand Up @@ -1485,9 +1495,92 @@ public virtual void WalkTo(IPoint3D target, short speed)
BroadcastUpdate();
}

private void StartArriveAtTargetAction(int requiredTicks)
public PathCalculator PathCalculator { get; protected set; }
/// <summary>
/// Finds a valid path to the destination (or picks the direct path otherwise). Uses WalkTo for each of the pathing nodes.
/// </summary>
/// <param name="dest"></param>
/// <param name="speed"></param>
/// <returns>true if a path was found</returns>
public bool PathTo(IPoint3D dest, short? speed = null)
{
var walkSpeed = speed ?? MaxSpeed;
if (!PathCalculator.ShouldPath(this, new Vector3(dest.X, dest.Y, dest.Z)))
{
WalkTo(dest, walkSpeed);
return false;
}

// Initialize pathing if possible and required
if (PathCalculator == null)
{
if (!PathCalculator.IsSupported(this))
{
WalkTo(dest, walkSpeed);
return false;
}
// TODO: Only make this check once on spawn since it internally calls .CurrentZone + hashtable lookup?
PathCalculator = new PathCalculator(this);
}

// Pick the next pathing node, and walk towards it
Vector3? nextNode = null;
bool didFindPath = false;
bool shouldUseAirPath = true;
if (PathCalculator != null)
{
var res = PathCalculator.CalculateNextTarget(new Vector3(dest.X, dest.Y, dest.Z));
nextNode = res.Item1;
var noPathReason = res.Item2;
shouldUseAirPath = noPathReason == NoPathReason.RECAST_FOUND_NO_PATH;
didFindPath = PathCalculator.DidFindPath;
}

// Directly walk towards the target (or call the customly provided action)
if (!nextNode.HasValue)
{
WalkTo(dest, walkSpeed);
return false;
}

// Do the actual pathing bit: Walk towards the next pathing node
WalkTo(nextNode.Value, walkSpeed, npc => npc.PathTo(dest, speed));
return true;
}

private void WalkTo(Vector3 node, short speed, Action<GameNPC> goToNextNodeCallback)
{
if (IsTurningDisabled)
return;

if (speed > MaxSpeed)
speed = MaxSpeed;

if (speed <= 0)
return;

TargetPosition = new Point3D(node.X, node.Y, node.Z); // this also saves the current position

if (IsWithinRadius(TargetPosition, 5))
{
goToNextNodeCallback(this);
return;
}

CancelWalkToTimer();

m_Heading = GetHeading(TargetPosition);
m_currentSpeed = speed;

UpdateTickSpeed();
StartArriveAtTargetAction(GetTicksToArriveAt(TargetPosition, speed), goToNextNodeCallback);
BroadcastUpdate();
}


private void StartArriveAtTargetAction(int requiredTicks, Action<GameNPC> goToNextNodeCallback = null)
{
m_arriveAtTargetAction = new ArriveAtTargetAction(this);
m_arriveAtTargetAction = new ArriveAtTargetAction(this, goToNextNodeCallback);
m_arriveAtTargetAction.Start((requiredTicks > 1) ? requiredTicks : 1);
}

Expand Down Expand Up @@ -1528,7 +1621,7 @@ public virtual void WalkToSpawn(short speed)

IsReturningHome = true;
IsReturningToSpawnPoint = true;
WalkTo(SpawnPoint, speed);
PathTo(SpawnPoint, speed);
}

/// <summary>
Expand Down
20 changes: 20 additions & 0 deletions GameServer/world/Pathing/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Numerics;

namespace DOL.GS
{
static class Extensions
{
public static bool IsInRange(this Vector3 value, Vector3 target, float range)
{
// SH: Removed Z checks when one of the two Z values is zero(on ground)
if (value.Z == 0 || target.Z == 0)
return Vector2.DistanceSquared(value.ToVector2(), target.ToVector2()) <= range * range;
return Vector3.DistanceSquared(value, target) <= range * range;
}

public static Vector2 ToVector2(this Vector3 value)
{
return new Vector2(value.X, value.Y);
}
}
}
55 changes: 55 additions & 0 deletions GameServer/world/Pathing/IPathingMgr.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Numerics;
using System.Threading.Tasks;

namespace DOL.GS
{
public interface IPathingMgr
{
/// <summary>
/// Initializes the PathingMgr by loading all available navmeshes
/// </summary>
/// <returns></returns>
bool Init();

/// <summary>
/// Stops the PathingMgr and releases all loaded navmeshes
/// </summary>
void Stop();

/// <summary>
/// Returns a path that prevents collisions with the navmesh, but floats freely otherwise
/// </summary>
/// <param name="zone"></param>
/// <param name="start">Start in GlobalXYZ</param>
/// <param name="end">End in GlobalXYZ</param>
/// <returns></returns>
WrappedPathingResult GetPathStraightAsync(Zone zone, Vector3 start, Vector3 end);

/// <summary>
/// Returns a random point on the navmesh around the given position
/// </summary>
/// <param name="zone">Zone</param>
/// <param name="position">Start in GlobalXYZ</param>
/// <param name="radius">End in GlobalXYZ</param>
/// <returns>null if no point found, Vector3 with point otherwise</returns>
Vector3? GetRandomPointAsync(Zone zone, Vector3 position, float radius);

/// <summary>
/// Returns the closest point on the navmesh, if available, or no point found.
/// Returns the input position if no navmesh is available
/// </summary>
Vector3? GetClosestPointAsync(Zone zone, Vector3 position, float xRange = 256f, float yRange = 256f, float zRange = 256f);

/// <summary>
/// True if pathing is enabled for the specified zone
/// </summary>
/// <param name="zone"></param>
/// <returns></returns>
bool HasNavmesh(Zone zone);

/// <summary>
/// True if currently running & working
/// </summary>
bool IsAvailable { get; }
}
}
Loading

0 comments on commit 86eb0b3

Please sign in to comment.