mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-14 00:20:51 +00:00
More progress.
- AMBUSH flag on nodes makes them blind and untargetable when getting `startnode` and `goalnode` for `FindPath()`. This is useful for indicating a node should be skipped when tele/portaling, so the current path can be preserved. - Setup is simple: place nodes behind lines that tele/portal entities and mark them as AMBUSH. Other changes: - Restored global array since blockmap is not a viable option here. - Added MAPINFO `pathing` flag which enables pathing by default. - Added NOPATHING flag to disable pathing entirely, useful for maps that have pathing enabled. - Added `ReachedNode(Actor mo)` virtual, responsible for handling node traversal. - Nodes now make use of MeleeRange to limit their sight checking functions.
This commit is contained in:
parent
ad52e2cc1e
commit
81ebd8c8c4
14 changed files with 210 additions and 82 deletions
|
@ -2506,26 +2506,25 @@ static void ReconstructPath(TMap<AActor*, AActor*> &cameFrom, AActor* current, T
|
|||
while(tmp = cameFrom.CheckKey(*tmp));
|
||||
}
|
||||
|
||||
static AActor* FindClosestNode(AActor* from, double maxSearch)
|
||||
static AActor* FindClosestNode(AActor* from)
|
||||
{
|
||||
static PClass * nodeCls = PClass::FindClass(NAME_PathNode);
|
||||
|
||||
FPortalGroupArray check(FPortalGroupArray::PGA_Full3d);
|
||||
FMultiBlockThingsIterator it(check, from->Level, from->Pos().X, from->Pos().Y, from->Pos().Z - ((from->Height + maxSearch) / 2), from->Height + maxSearch, from->radius + maxSearch, false, from->Sector);
|
||||
FMultiBlockThingsIterator::CheckResult res;
|
||||
|
||||
AActor * closest = nullptr;
|
||||
double closestDist = DBL_MAX;
|
||||
|
||||
while(it.Next(&res))
|
||||
for (int i = 0; i < from->Level->PathNodes.Size(); i++)
|
||||
{
|
||||
if(nodeCls->IsAncestorOf(res.thing->GetClass()))
|
||||
AActor* node = from->Level->PathNodes[i];
|
||||
if(node && !(node->flags & MF_AMBUSH) && nodeCls->IsAncestorOf(node->GetClass()))
|
||||
{
|
||||
double dst = res.thing->Distance3D(from);
|
||||
if(dst < closestDist && P_CheckSight(res.thing, from))
|
||||
double dst = node->Distance3DSquared(from);
|
||||
bool mrange = (dst < closestDist && (node->meleerange <= 0.0 || dst < (node->meleerange * node->meleerange)));
|
||||
|
||||
if(mrange && P_CheckSight(node, from))
|
||||
{
|
||||
closestDist = dst;
|
||||
closest = res.thing;
|
||||
closest = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2540,7 +2539,7 @@ static V GetOr(TMap<K, V> map, const K &key, V alt)
|
|||
return k ? *k : alt;
|
||||
}
|
||||
|
||||
static bool FindPathAStar(AActor* start, AActor* goal, TArray<TObjPtr<AActor*>> &path)
|
||||
static bool FindPathAStar(AActor* startnode, AActor* goalnode, TArray<TObjPtr<AActor*>> &path)
|
||||
{
|
||||
|
||||
TArray<AActor*> openSet;
|
||||
|
@ -2548,9 +2547,9 @@ static bool FindPathAStar(AActor* start, AActor* goal, TArray<TObjPtr<AActor*>>
|
|||
TMap<AActor*, double> gScore;
|
||||
TMap<AActor*, double> fScore;
|
||||
|
||||
openSet.Push(start);
|
||||
gScore.Insert(start, 0);
|
||||
fScore.Insert(start, start->Distance3D(goal));
|
||||
openSet.Push(startnode);
|
||||
gScore.Insert(startnode, 0);
|
||||
fScore.Insert(startnode, startnode->Distance3DSquared(goalnode));
|
||||
|
||||
auto lt_fScore = [&fScore](AActor* lhs, AActor* rhs)
|
||||
{
|
||||
|
@ -2561,7 +2560,7 @@ static bool FindPathAStar(AActor* start, AActor* goal, TArray<TObjPtr<AActor*>>
|
|||
{
|
||||
AActor * current = openSet[0];
|
||||
openSet.Delete(0);
|
||||
if(current == goal)
|
||||
if(current == goalnode)
|
||||
{
|
||||
ReconstructPath(cameFrom, current, path);
|
||||
return true;
|
||||
|
@ -2572,7 +2571,7 @@ static bool FindPathAStar(AActor* start, AActor* goal, TArray<TObjPtr<AActor*>>
|
|||
for(AActor * neighbor : GetPathNodeNeighbors(current))
|
||||
{
|
||||
|
||||
double tentative_gScore = current_gScore + current->Distance3D(neighbor);
|
||||
double tentative_gScore = current_gScore + current->Distance3DSquared(neighbor);
|
||||
|
||||
double neighbor_gScore = GetOr(gScore, neighbor, DBL_MAX);
|
||||
|
||||
|
@ -2581,7 +2580,7 @@ static bool FindPathAStar(AActor* start, AActor* goal, TArray<TObjPtr<AActor*>>
|
|||
openSet.SortedDelete(neighbor, lt_fScore);
|
||||
cameFrom.Insert(neighbor, current);
|
||||
gScore.Insert(neighbor, tentative_gScore);
|
||||
fScore.Insert(neighbor, tentative_gScore + neighbor->Distance3D(goal));
|
||||
fScore.Insert(neighbor, tentative_gScore + neighbor->Distance3DSquared(goalnode));
|
||||
openSet.SortedInsert(neighbor, lt_fScore);
|
||||
}
|
||||
}
|
||||
|
@ -2589,20 +2588,19 @@ static bool FindPathAStar(AActor* start, AActor* goal, TArray<TObjPtr<AActor*>>
|
|||
return false;
|
||||
}
|
||||
|
||||
bool FLevelLocals::FindPath(AActor* start, AActor* goal, AActor* startNode, AActor* goalNode, double maxSearch)
|
||||
bool FLevelLocals::FindPath(AActor* chaser, AActor* target, AActor* startNode, AActor* goalNode)
|
||||
{
|
||||
static PClass * nodeCls = PClass::FindClass(NAME_PathNode);
|
||||
|
||||
if (!start || !goal)
|
||||
if (!chaser || !target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static PClass* nodeCls = PClass::FindClass(NAME_PathNode);
|
||||
assert(startNode == nullptr || nodeCls->IsAncestorOf(startNode->GetClass()));
|
||||
assert(goalNode == nullptr || nodeCls->IsAncestorOf(goalNode->GetClass()));
|
||||
|
||||
if(startNode == nullptr) startNode = FindClosestNode(start, maxSearch);
|
||||
if(goalNode == nullptr) goalNode = FindClosestNode(goal, maxSearch);
|
||||
if(startNode == nullptr) startNode = FindClosestNode(chaser);
|
||||
if(goalNode == nullptr) goalNode = FindClosestNode(target);
|
||||
|
||||
// Incomplete graph.
|
||||
if (!startNode || !goalNode)
|
||||
|
@ -2612,16 +2610,16 @@ bool FLevelLocals::FindPath(AActor* start, AActor* goal, AActor* startNode, AAct
|
|||
|
||||
if (startNode == goalNode)
|
||||
{
|
||||
start->ClearPath();
|
||||
start->Path.Push(MakeObjPtr<AActor*>(startNode));
|
||||
chaser->ClearPath();
|
||||
chaser->Path.Push(MakeObjPtr<AActor*>(startNode));
|
||||
return true;
|
||||
}
|
||||
|
||||
if(FindPathAStar(startNode, goalNode, start->Path))
|
||||
if (FindPathAStar(startNode, goalNode, chaser->Path))
|
||||
{
|
||||
if (start->goal && nodeCls->IsAncestorOf(start->goal->GetClass()))
|
||||
if (chaser->goal && nodeCls->IsAncestorOf(chaser->goal->GetClass()))
|
||||
{
|
||||
start->goal = nullptr;
|
||||
chaser->goal = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -2636,6 +2634,22 @@ DEFINE_ACTION_FUNCTION(FLevelLocals, FindPath)
|
|||
PARAM_OBJECT(target, AActor);
|
||||
PARAM_OBJECT(startnode, AActor);
|
||||
PARAM_OBJECT(goalnode, AActor);
|
||||
PARAM_FLOAT(maxSearch);
|
||||
return self->FindPath(chaser, target, startnode, goalnode, maxSearch);
|
||||
return self->FindPath(chaser, target, startnode, goalnode);
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(FLevelLocals, HandlePathNode)
|
||||
{
|
||||
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
|
||||
PARAM_OBJECT(node, AActor);
|
||||
PARAM_BOOL(add);
|
||||
if (node)
|
||||
{
|
||||
if (add)
|
||||
{
|
||||
if (self->PathNodes.Find(node) >= self->PathNodes.Size())
|
||||
self->PathNodes.Push(node);
|
||||
}
|
||||
else self->PathNodes.Delete(self->PathNodes.Find(node));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -444,8 +444,9 @@ public:
|
|||
|
||||
void SetMusic();
|
||||
|
||||
bool FindPath(AActor *chaser, AActor *target, AActor *startnode = nullptr, AActor *goalnode = nullptr, double maxSearch = 256.0);
|
||||
bool FindPath(AActor *chaser, AActor *target, AActor *startnode = nullptr, AActor *goalnode = nullptr);
|
||||
|
||||
TArray<AActor *> PathNodes;
|
||||
TArray<vertex_t> vertexes;
|
||||
TArray<sector_t> sectors;
|
||||
TArray<extsector_t> extsectors; // container for non-trivial sector information. sector_t must be trivially copyable for *_fakeflat to work as intended.
|
||||
|
|
|
@ -1819,6 +1819,7 @@ MapFlagHandlers[] =
|
|||
{ "disableskyboxao", MITYPE_CLRFLAG3, LEVEL3_SKYBOXAO, 0 },
|
||||
{ "avoidmelee", MITYPE_SETFLAG3, LEVEL3_AVOIDMELEE, 0 },
|
||||
{ "attenuatelights", MITYPE_SETFLAG3, LEVEL3_ATTENUATE, 0 },
|
||||
{ "pathing", MITYPE_SETFLAG3, LEVEL3_PATHING, 0 },
|
||||
{ "nobotnodes", MITYPE_IGNORE, 0, 0 }, // Skulltag option: nobotnodes
|
||||
{ "nopassover", MITYPE_COMPATFLAG, COMPATF_NO_PASSMOBJ, 0 },
|
||||
{ "passover", MITYPE_CLRCOMPATFLAG, COMPATF_NO_PASSMOBJ, 0 },
|
||||
|
|
|
@ -270,6 +270,7 @@ enum ELevelFlags : unsigned int
|
|||
LEVEL3_AVOIDMELEE = 0x00020000, // global flag needed for proper MBF support.
|
||||
LEVEL3_NOJUMPDOWN = 0x00040000, // only for MBF21. Inverse of MBF's dog_jumping flag.
|
||||
LEVEL3_LIGHTCREATED = 0x00080000, // a light had been created in the last frame
|
||||
LEVEL3_PATHING = 0x00100000, // enable pathfinding by default
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -326,6 +326,7 @@ void FLevelLocals::ClearLevelData(bool fullgc)
|
|||
}
|
||||
ClearPortals();
|
||||
|
||||
PathNodes.Clear();
|
||||
tagManager.Clear();
|
||||
ClearTIDHashes();
|
||||
if (SpotState) SpotState->Destroy();
|
||||
|
|
|
@ -445,6 +445,7 @@ enum ActorFlag9
|
|||
MF9_DECOUPLEDANIMATIONS = 0x00000010, // [RL0] Decouple model animations from states
|
||||
MF9_PATHING = 0x00000020, // [MC] Enables monsters to do pathfinding, such as A*.
|
||||
MF9_KEEPPATH = 0x00000040, // [MC] Forces monsters to keep to the path when target's in sight.
|
||||
MF9_NOPATHING = 0x00000080, // [MC] override the mapinfo "pathfinding"
|
||||
};
|
||||
|
||||
// --- mobj.renderflags ---
|
||||
|
@ -1104,6 +1105,8 @@ public:
|
|||
void SetDynamicLights();
|
||||
|
||||
void ClearPath();
|
||||
bool CanPathfind();
|
||||
void CallReachedNode(AActor *node);
|
||||
|
||||
// info for drawing
|
||||
// NOTE: The first member variable *must* be snext.
|
||||
|
|
|
@ -2208,8 +2208,12 @@ DEFINE_ACTION_FUNCTION(AActor, A_ClearLastHeard)
|
|||
void AActor::ClearPath()
|
||||
{
|
||||
Path.Clear();
|
||||
if (goal && goal->IsKindOf(NAME_PathNode))
|
||||
if (goal)
|
||||
{
|
||||
static PClass* nodeCls = PClass::FindClass(NAME_PathNode);
|
||||
if (nodeCls->IsAncestorOf(goal->GetClass()))
|
||||
goal = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, ClearPath)
|
||||
|
@ -2219,6 +2223,42 @@ DEFINE_ACTION_FUNCTION(AActor, ClearPath)
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool AActor::CanPathfind()
|
||||
{
|
||||
if ((!(flags9 & MF9_NOPATHING) && !(Sector->MoreFlags & SECMF_NOPATHING)) &&
|
||||
(flags9 & MF9_PATHING || Level->flags3 & LEVEL3_PATHING))
|
||||
{
|
||||
if ((flags6 & MF6_NOFEAR))
|
||||
return true;
|
||||
|
||||
// Can't pathfind while feared.
|
||||
if (!(flags4 & MF4_FRIGHTENED))
|
||||
{
|
||||
if (!target)
|
||||
return true;
|
||||
|
||||
if (!target->flags8 & MF8_FRIGHTENING)
|
||||
return (!target->player || !(target->player->cheats & CF_FRIGHTENING));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, CanPathfind)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
return self->CanPathfind();
|
||||
}
|
||||
|
||||
void AActor::CallReachedNode(AActor *node)
|
||||
{
|
||||
IFVIRTUAL(AActor, ReachedNode)
|
||||
{
|
||||
VMValue params[2] = { this, node };
|
||||
VMCall(func, params, 2, nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// A_Wander
|
||||
|
@ -2515,30 +2555,8 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi
|
|||
return;
|
||||
}
|
||||
|
||||
if (actor->target && actor->flags9 & MF9_PATHING)
|
||||
{
|
||||
if (actor->goal && actor->goal->IsKindOf(NAME_PathNode))
|
||||
{
|
||||
AActor* temp = actor->target;
|
||||
actor->target = actor->goal;
|
||||
bool result = P_CheckMeleeRange(actor);
|
||||
actor->target = temp;
|
||||
|
||||
if (result) // TO DO
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
if (!actor->goal)
|
||||
{
|
||||
if (actor->Path.Size() < 1 && actor->Level->FindPath(actor, actor->target))
|
||||
actor->goal = actor->Path[0];
|
||||
|
||||
}
|
||||
}
|
||||
// [RH] Don't attack if just moving toward goal
|
||||
else if (actor->target == actor->goal || (actor->flags5&MF5_CHASEGOAL && actor->goal != nullptr))
|
||||
if (actor->target == actor->goal || (actor->flags5&MF5_CHASEGOAL && actor->goal != nullptr))
|
||||
{
|
||||
AActor * savedtarget = actor->target;
|
||||
actor->target = actor->goal;
|
||||
|
@ -2617,7 +2635,45 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (actor->target && actor->CanPathfind())
|
||||
{
|
||||
if (actor->goal && !(actor->goal->flags & MF_AMBUSH) && actor->goal->IsKindOf(NAME_PathNode))
|
||||
{
|
||||
AActor* temp = actor->target;
|
||||
actor->target = actor->goal;
|
||||
bool reached = (P_CheckMeleeRange(actor));
|
||||
actor->target = temp;
|
||||
|
||||
if (reached)
|
||||
{
|
||||
actor->CallReachedNode(actor->goal);
|
||||
/*
|
||||
AActor* next = nullptr;
|
||||
if (!(actor->flags9 & MF9_KEEPPATH) &&
|
||||
P_CheckSight(actor, actor->target, SF_IGNOREWATERBOUNDARY | SF_IGNOREVISIBILITY))
|
||||
actor->Path.Clear();
|
||||
else
|
||||
{
|
||||
unsigned int index = actor->Path.Find(actor->goal);
|
||||
while (++index < actor->Path.Size() - 1)
|
||||
{
|
||||
next = actor->Path[index];
|
||||
if (next && next != actor->goal)
|
||||
break;
|
||||
}
|
||||
if (!next) actor->ClearPath();
|
||||
else actor->goal = next;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
if (!actor->goal)
|
||||
{
|
||||
if (actor->Path.Size() > 0 || actor->Level->FindPath(actor, actor->target))
|
||||
actor->goal = actor->Path[0];
|
||||
}
|
||||
}
|
||||
|
||||
// [RH] Scared monsters attack less frequently
|
||||
|
@ -2670,7 +2726,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi
|
|||
lookForBetter = true;
|
||||
}
|
||||
AActor * oldtarget = actor->target;
|
||||
gotNew = P_LookForPlayers (actor, !(flags & CHF_DONTLOOKALLAROUND), NULL);
|
||||
gotNew = P_LookForPlayers (actor, !(flags & CHF_DONTLOOKALLAROUND), nullptr);
|
||||
if (lookForBetter)
|
||||
{
|
||||
actor->flags3 |= MF3_NOSIGHTCHECK;
|
||||
|
@ -2678,6 +2734,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi
|
|||
if (gotNew && actor->target != oldtarget)
|
||||
{
|
||||
actor->flags7 &= ~MF7_INCHASE;
|
||||
actor->ClearPath();
|
||||
return; // got a new target
|
||||
}
|
||||
}
|
||||
|
|
|
@ -592,7 +592,7 @@ bool P_TeleportMove(AActor* thing, const DVector3 &pos, bool telefrag, bool modi
|
|||
thing->CheckSectorTransition(oldsec);
|
||||
}
|
||||
}
|
||||
|
||||
thing->CallReachedNode(thing->goal);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2613,6 +2613,7 @@ bool P_TryMove(AActor *thing, const DVector2 &pos,
|
|||
thing->LinkToWorld(&ctx);
|
||||
P_FindFloorCeiling(thing);
|
||||
thing->ClearInterpolation();
|
||||
thing->CallReachedNode(thing->goal);
|
||||
portalcrossed = true;
|
||||
tm.portalstep = false;
|
||||
}
|
||||
|
|
|
@ -355,6 +355,7 @@ static FFlagDef ActorFlagDefs[]=
|
|||
DEFINE_FLAG(MF9, DECOUPLEDANIMATIONS, AActor, flags9),
|
||||
DEFINE_FLAG(MF9, PATHING, AActor, flags9),
|
||||
DEFINE_FLAG(MF9, KEEPPATH, AActor, flags9),
|
||||
DEFINE_FLAG(MF9, NOPATHING, AActor, flags9),
|
||||
|
||||
// Effect flags
|
||||
DEFINE_FLAG(FX, VISIBILITYPULSE, AActor, effects),
|
||||
|
|
|
@ -2786,6 +2786,7 @@ DEFINE_FIELD_X(LevelInfo, level_info_t, RedirectMapName)
|
|||
DEFINE_FIELD_X(LevelInfo, level_info_t, teamdamage)
|
||||
|
||||
DEFINE_GLOBAL_NAMED(currentVMLevel, level)
|
||||
DEFINE_FIELD(FLevelLocals, PathNodes)
|
||||
DEFINE_FIELD(FLevelLocals, sectors)
|
||||
DEFINE_FIELD(FLevelLocals, lines)
|
||||
DEFINE_FIELD(FLevelLocals, sides)
|
||||
|
@ -2853,6 +2854,7 @@ DEFINE_FIELD_BIT(FLevelLocals, flags2, infinite_flight, LEVEL2_INFINITE_FLIGHT)
|
|||
DEFINE_FIELD_BIT(FLevelLocals, flags2, no_dlg_freeze, LEVEL2_CONV_SINGLE_UNFREEZE)
|
||||
DEFINE_FIELD_BIT(FLevelLocals, flags2, keepfullinventory, LEVEL2_KEEPFULLINVENTORY)
|
||||
DEFINE_FIELD_BIT(FLevelLocals, flags3, removeitems, LEVEL3_REMOVEITEMS)
|
||||
DEFINE_FIELD_BIT(FLevelLocals, flags3, pathing, LEVEL3_PATHING)
|
||||
|
||||
DEFINE_FIELD_X(Sector, sector_t, floorplane)
|
||||
DEFINE_FIELD_X(Sector, sector_t, ceilingplane)
|
||||
|
|
|
@ -44,6 +44,7 @@ DoomEdNums
|
|||
5065 = InvisibleBridge8
|
||||
9001 = MapSpot
|
||||
9013 = MapSpotGravity
|
||||
9022 = PathNode
|
||||
9024 = PatrolPoint
|
||||
9025 = SecurityCamera
|
||||
9026 = Spark
|
||||
|
@ -285,7 +286,6 @@ DoomEdNums
|
|||
14163 = MusicChanger, 63
|
||||
14164 = MusicChanger, 64
|
||||
14165 = MusicChanger
|
||||
14166 = PathNode
|
||||
32000 = DoomBuilderCamera
|
||||
}
|
||||
|
||||
|
|
|
@ -664,7 +664,7 @@ class Actor : Thinker native
|
|||
// called before and after triggering a teleporter
|
||||
// return false in PreTeleport() to cancel the action early
|
||||
virtual bool PreTeleport( Vector3 destpos, double destangle, int flags ) { return true; }
|
||||
virtual void PostTeleport( Vector3 destpos, double destangle, int flags ) {}
|
||||
virtual void PostTeleport( Vector3 destpos, double destangle, int flags ) { }
|
||||
|
||||
native virtual bool OkayToSwitchTarget(Actor other);
|
||||
native clearscope static class<Actor> GetReplacement(class<Actor> cls);
|
||||
|
@ -706,7 +706,6 @@ class Actor : Thinker native
|
|||
native void ClearFOVInterpolation();
|
||||
native clearscope Vector3 PosRelative(sector sec) const;
|
||||
native void RailAttack(FRailParams p);
|
||||
native void ClearPath();
|
||||
|
||||
native void HandleSpawnFlags();
|
||||
native void ExplodeMissile(line lin = null, Actor target = null, bool onsky = false);
|
||||
|
@ -799,6 +798,46 @@ class Actor : Thinker native
|
|||
return true;
|
||||
}
|
||||
|
||||
native void ClearPath();
|
||||
native clearscope bool CanPathfind() const;
|
||||
virtual void ReachedNode(Actor mo)
|
||||
{
|
||||
if (!mo && !goal)
|
||||
return;
|
||||
|
||||
mo = goal;
|
||||
let node = PathNode(mo);
|
||||
if (!node || !target || (!bKEEPPATH && CheckSight(target)))
|
||||
{
|
||||
ClearPath();
|
||||
return;
|
||||
}
|
||||
|
||||
int i = Path.Find(node) + 1;
|
||||
int end = Path.Size();
|
||||
|
||||
for (i; i < end; i++)
|
||||
{
|
||||
PathNode next = Path[i];
|
||||
|
||||
if (!next || next == node)
|
||||
continue;
|
||||
|
||||
// Monsters will never 'reach' AMBUSH flagged nodes. Instead, the engine
|
||||
// indicates they're reached the moment they tele/portal.
|
||||
|
||||
if (node.bAMBUSH && next.bAMBUSH)
|
||||
continue;
|
||||
|
||||
goal = next;
|
||||
break;
|
||||
}
|
||||
|
||||
if (i >= end)
|
||||
ClearPath();
|
||||
|
||||
}
|
||||
|
||||
native bool TryMove(vector2 newpos, int dropoff, bool missilecheck = false, FCheckPosition tm = null);
|
||||
native bool CheckMove(vector2 newpos, int flags = 0, FCheckPosition tm = null);
|
||||
native void NewChaseDir();
|
||||
|
|
|
@ -246,36 +246,34 @@ class SpeakerIcon : Unknown
|
|||
}
|
||||
}
|
||||
|
||||
//===============================================================
|
||||
// Path Nodes
|
||||
//===============================================================
|
||||
/*
|
||||
=================================================================
|
||||
Path Nodes
|
||||
=================================================================
|
||||
Special flags are as follows:
|
||||
|
||||
AMBUSH
|
||||
Node is blind. Things cannot "touch" these nodes with A_Chase.
|
||||
Useful for tele/portals since the engine makes them "touch"
|
||||
upon transitioning. These nodes are fast forwarded over in Actor's
|
||||
ReachedNode() function.
|
||||
*/
|
||||
class PathNode : Actor
|
||||
{
|
||||
// For non-connected paths. Stamina will be used to set this. Necessary for tele/portals.
|
||||
private int group;
|
||||
|
||||
Array<PathNode> neighbors;
|
||||
|
||||
Default
|
||||
{
|
||||
//$Arg0 "TID 1"
|
||||
//$Arg1 "TID 2"
|
||||
//$Arg2 "TID 3"
|
||||
//$Arg3 "TID 4"
|
||||
//$Arg4 "TID 5"
|
||||
//$Arg0Type 14
|
||||
//$Arg1Type 14
|
||||
//$Arg2Type 14
|
||||
//$Arg3Type 14
|
||||
//$Arg4Type 14
|
||||
+NOINTERACTION
|
||||
+NOBLOCKMAP
|
||||
+INVISIBLE
|
||||
+DONTSPLASH
|
||||
+NOTONAUTOMAP
|
||||
+NOGRAVITY // TO DO: Look into 3D variant for traversing up and down 3D floors and floating monsters.
|
||||
+NOGRAVITY
|
||||
Radius 16;
|
||||
Height 56;
|
||||
RenderStyle "None";
|
||||
MeleeRange 2048; // Sight checks limited to this. 0 = infinite.
|
||||
MeleeRange 0; // Sight checks limited to this. 0 = infinite. Set within map editor.
|
||||
}
|
||||
|
||||
// Args are TIDs. Can be one way to force single directions.
|
||||
|
@ -295,6 +293,13 @@ class PathNode : Actor
|
|||
} while (node = PathNode(it.Next()))
|
||||
|
||||
}
|
||||
level.HandlePathNode(self, true);
|
||||
}
|
||||
|
||||
override void OnDestroy()
|
||||
{
|
||||
level.HandlePathNode(self, false);
|
||||
Super.OnDestroy();
|
||||
}
|
||||
|
||||
// For ACS access with ScriptCall.
|
||||
|
|
|
@ -408,7 +408,7 @@ struct LevelLocals native
|
|||
|
||||
const CLUSTER_HUB = 0x00000001; // Cluster uses hub behavior
|
||||
|
||||
|
||||
native readonly Array<PathNode> PathNodes;
|
||||
native Array<@Sector> Sectors;
|
||||
native Array<@Line> Lines;
|
||||
native Array<@Side> Sides;
|
||||
|
@ -476,6 +476,7 @@ struct LevelLocals native
|
|||
native readonly int compatflags;
|
||||
native readonly int compatflags2;
|
||||
native readonly LevelInfo info;
|
||||
native readonly bool pathing;
|
||||
|
||||
native String GetUDMFString(int type, int index, Name key);
|
||||
native int GetUDMFInt(int type, int index, Name key);
|
||||
|
@ -554,7 +555,8 @@ struct LevelLocals native
|
|||
native void SpawnParticle(FSpawnParticleParams p);
|
||||
native VisualThinker SpawnVisualThinker(Class<VisualThinker> type);
|
||||
|
||||
native bool FindPath(Actor chaser, Actor target, Actor startnode = null, Actor goalnode = null, double maxSearch = 256.0);
|
||||
native bool FindPath(Actor chaser, Actor target, PathNode startnode = null, PathNode goalnode = null);
|
||||
native void HandlePathNode(PathNode node, bool add);
|
||||
}
|
||||
|
||||
// a few values of this need to be readable by the play code.
|
||||
|
|
Loading…
Reference in a new issue