mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-14 16:40:56 +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));
|
while(tmp = cameFrom.CheckKey(*tmp));
|
||||||
}
|
}
|
||||||
|
|
||||||
static AActor* FindClosestNode(AActor* from, double maxSearch)
|
static AActor* FindClosestNode(AActor* from)
|
||||||
{
|
{
|
||||||
static PClass * nodeCls = PClass::FindClass(NAME_PathNode);
|
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;
|
AActor * closest = nullptr;
|
||||||
double closestDist = DBL_MAX;
|
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);
|
double dst = node->Distance3DSquared(from);
|
||||||
if(dst < closestDist && P_CheckSight(res.thing, from))
|
bool mrange = (dst < closestDist && (node->meleerange <= 0.0 || dst < (node->meleerange * node->meleerange)));
|
||||||
|
|
||||||
|
if(mrange && P_CheckSight(node, from))
|
||||||
{
|
{
|
||||||
closestDist = dst;
|
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;
|
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;
|
TArray<AActor*> openSet;
|
||||||
|
@ -2548,9 +2547,9 @@ static bool FindPathAStar(AActor* start, AActor* goal, TArray<TObjPtr<AActor*>>
|
||||||
TMap<AActor*, double> gScore;
|
TMap<AActor*, double> gScore;
|
||||||
TMap<AActor*, double> fScore;
|
TMap<AActor*, double> fScore;
|
||||||
|
|
||||||
openSet.Push(start);
|
openSet.Push(startnode);
|
||||||
gScore.Insert(start, 0);
|
gScore.Insert(startnode, 0);
|
||||||
fScore.Insert(start, start->Distance3D(goal));
|
fScore.Insert(startnode, startnode->Distance3DSquared(goalnode));
|
||||||
|
|
||||||
auto lt_fScore = [&fScore](AActor* lhs, AActor* rhs)
|
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];
|
AActor * current = openSet[0];
|
||||||
openSet.Delete(0);
|
openSet.Delete(0);
|
||||||
if(current == goal)
|
if(current == goalnode)
|
||||||
{
|
{
|
||||||
ReconstructPath(cameFrom, current, path);
|
ReconstructPath(cameFrom, current, path);
|
||||||
return true;
|
return true;
|
||||||
|
@ -2572,7 +2571,7 @@ static bool FindPathAStar(AActor* start, AActor* goal, TArray<TObjPtr<AActor*>>
|
||||||
for(AActor * neighbor : GetPathNodeNeighbors(current))
|
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);
|
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);
|
openSet.SortedDelete(neighbor, lt_fScore);
|
||||||
cameFrom.Insert(neighbor, current);
|
cameFrom.Insert(neighbor, current);
|
||||||
gScore.Insert(neighbor, tentative_gScore);
|
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);
|
openSet.SortedInsert(neighbor, lt_fScore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2589,20 +2588,19 @@ static bool FindPathAStar(AActor* start, AActor* goal, TArray<TObjPtr<AActor*>>
|
||||||
return false;
|
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 (!chaser || !target)
|
||||||
|
|
||||||
if (!start || !goal)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PClass* nodeCls = PClass::FindClass(NAME_PathNode);
|
||||||
assert(startNode == nullptr || nodeCls->IsAncestorOf(startNode->GetClass()));
|
assert(startNode == nullptr || nodeCls->IsAncestorOf(startNode->GetClass()));
|
||||||
assert(goalNode == nullptr || nodeCls->IsAncestorOf(goalNode->GetClass()));
|
assert(goalNode == nullptr || nodeCls->IsAncestorOf(goalNode->GetClass()));
|
||||||
|
|
||||||
if(startNode == nullptr) startNode = FindClosestNode(start, maxSearch);
|
if(startNode == nullptr) startNode = FindClosestNode(chaser);
|
||||||
if(goalNode == nullptr) goalNode = FindClosestNode(goal, maxSearch);
|
if(goalNode == nullptr) goalNode = FindClosestNode(target);
|
||||||
|
|
||||||
// Incomplete graph.
|
// Incomplete graph.
|
||||||
if (!startNode || !goalNode)
|
if (!startNode || !goalNode)
|
||||||
|
@ -2612,16 +2610,16 @@ bool FLevelLocals::FindPath(AActor* start, AActor* goal, AActor* startNode, AAct
|
||||||
|
|
||||||
if (startNode == goalNode)
|
if (startNode == goalNode)
|
||||||
{
|
{
|
||||||
start->ClearPath();
|
chaser->ClearPath();
|
||||||
start->Path.Push(MakeObjPtr<AActor*>(startNode));
|
chaser->Path.Push(MakeObjPtr<AActor*>(startNode));
|
||||||
return true;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2636,6 +2634,22 @@ DEFINE_ACTION_FUNCTION(FLevelLocals, FindPath)
|
||||||
PARAM_OBJECT(target, AActor);
|
PARAM_OBJECT(target, AActor);
|
||||||
PARAM_OBJECT(startnode, AActor);
|
PARAM_OBJECT(startnode, AActor);
|
||||||
PARAM_OBJECT(goalnode, AActor);
|
PARAM_OBJECT(goalnode, AActor);
|
||||||
PARAM_FLOAT(maxSearch);
|
return self->FindPath(chaser, target, startnode, goalnode);
|
||||||
return self->FindPath(chaser, target, startnode, goalnode, maxSearch);
|
}
|
||||||
|
|
||||||
|
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();
|
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<vertex_t> vertexes;
|
||||||
TArray<sector_t> sectors;
|
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.
|
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 },
|
{ "disableskyboxao", MITYPE_CLRFLAG3, LEVEL3_SKYBOXAO, 0 },
|
||||||
{ "avoidmelee", MITYPE_SETFLAG3, LEVEL3_AVOIDMELEE, 0 },
|
{ "avoidmelee", MITYPE_SETFLAG3, LEVEL3_AVOIDMELEE, 0 },
|
||||||
{ "attenuatelights", MITYPE_SETFLAG3, LEVEL3_ATTENUATE, 0 },
|
{ "attenuatelights", MITYPE_SETFLAG3, LEVEL3_ATTENUATE, 0 },
|
||||||
|
{ "pathing", MITYPE_SETFLAG3, LEVEL3_PATHING, 0 },
|
||||||
{ "nobotnodes", MITYPE_IGNORE, 0, 0 }, // Skulltag option: nobotnodes
|
{ "nobotnodes", MITYPE_IGNORE, 0, 0 }, // Skulltag option: nobotnodes
|
||||||
{ "nopassover", MITYPE_COMPATFLAG, COMPATF_NO_PASSMOBJ, 0 },
|
{ "nopassover", MITYPE_COMPATFLAG, COMPATF_NO_PASSMOBJ, 0 },
|
||||||
{ "passover", MITYPE_CLRCOMPATFLAG, 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_AVOIDMELEE = 0x00020000, // global flag needed for proper MBF support.
|
||||||
LEVEL3_NOJUMPDOWN = 0x00040000, // only for MBF21. Inverse of MBF's dog_jumping flag.
|
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_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();
|
ClearPortals();
|
||||||
|
|
||||||
|
PathNodes.Clear();
|
||||||
tagManager.Clear();
|
tagManager.Clear();
|
||||||
ClearTIDHashes();
|
ClearTIDHashes();
|
||||||
if (SpotState) SpotState->Destroy();
|
if (SpotState) SpotState->Destroy();
|
||||||
|
|
|
@ -445,6 +445,7 @@ enum ActorFlag9
|
||||||
MF9_DECOUPLEDANIMATIONS = 0x00000010, // [RL0] Decouple model animations from states
|
MF9_DECOUPLEDANIMATIONS = 0x00000010, // [RL0] Decouple model animations from states
|
||||||
MF9_PATHING = 0x00000020, // [MC] Enables monsters to do pathfinding, such as A*.
|
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_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 ---
|
// --- mobj.renderflags ---
|
||||||
|
@ -1104,6 +1105,8 @@ public:
|
||||||
void SetDynamicLights();
|
void SetDynamicLights();
|
||||||
|
|
||||||
void ClearPath();
|
void ClearPath();
|
||||||
|
bool CanPathfind();
|
||||||
|
void CallReachedNode(AActor *node);
|
||||||
|
|
||||||
// info for drawing
|
// info for drawing
|
||||||
// NOTE: The first member variable *must* be snext.
|
// NOTE: The first member variable *must* be snext.
|
||||||
|
|
|
@ -2208,8 +2208,12 @@ DEFINE_ACTION_FUNCTION(AActor, A_ClearLastHeard)
|
||||||
void AActor::ClearPath()
|
void AActor::ClearPath()
|
||||||
{
|
{
|
||||||
Path.Clear();
|
Path.Clear();
|
||||||
if (goal && goal->IsKindOf(NAME_PathNode))
|
if (goal)
|
||||||
|
{
|
||||||
|
static PClass* nodeCls = PClass::FindClass(NAME_PathNode);
|
||||||
|
if (nodeCls->IsAncestorOf(goal->GetClass()))
|
||||||
goal = nullptr;
|
goal = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_ACTION_FUNCTION(AActor, ClearPath)
|
DEFINE_ACTION_FUNCTION(AActor, ClearPath)
|
||||||
|
@ -2219,6 +2223,42 @@ DEFINE_ACTION_FUNCTION(AActor, ClearPath)
|
||||||
return 0;
|
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
|
// A_Wander
|
||||||
|
@ -2515,30 +2555,8 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi
|
||||||
return;
|
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
|
// [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;
|
AActor * savedtarget = actor->target;
|
||||||
actor->target = actor->goal;
|
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
|
// [RH] Scared monsters attack less frequently
|
||||||
|
@ -2670,7 +2726,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi
|
||||||
lookForBetter = true;
|
lookForBetter = true;
|
||||||
}
|
}
|
||||||
AActor * oldtarget = actor->target;
|
AActor * oldtarget = actor->target;
|
||||||
gotNew = P_LookForPlayers (actor, !(flags & CHF_DONTLOOKALLAROUND), NULL);
|
gotNew = P_LookForPlayers (actor, !(flags & CHF_DONTLOOKALLAROUND), nullptr);
|
||||||
if (lookForBetter)
|
if (lookForBetter)
|
||||||
{
|
{
|
||||||
actor->flags3 |= MF3_NOSIGHTCHECK;
|
actor->flags3 |= MF3_NOSIGHTCHECK;
|
||||||
|
@ -2678,6 +2734,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi
|
||||||
if (gotNew && actor->target != oldtarget)
|
if (gotNew && actor->target != oldtarget)
|
||||||
{
|
{
|
||||||
actor->flags7 &= ~MF7_INCHASE;
|
actor->flags7 &= ~MF7_INCHASE;
|
||||||
|
actor->ClearPath();
|
||||||
return; // got a new target
|
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->CheckSectorTransition(oldsec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
thing->CallReachedNode(thing->goal);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2613,6 +2613,7 @@ bool P_TryMove(AActor *thing, const DVector2 &pos,
|
||||||
thing->LinkToWorld(&ctx);
|
thing->LinkToWorld(&ctx);
|
||||||
P_FindFloorCeiling(thing);
|
P_FindFloorCeiling(thing);
|
||||||
thing->ClearInterpolation();
|
thing->ClearInterpolation();
|
||||||
|
thing->CallReachedNode(thing->goal);
|
||||||
portalcrossed = true;
|
portalcrossed = true;
|
||||||
tm.portalstep = false;
|
tm.portalstep = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -355,6 +355,7 @@ static FFlagDef ActorFlagDefs[]=
|
||||||
DEFINE_FLAG(MF9, DECOUPLEDANIMATIONS, AActor, flags9),
|
DEFINE_FLAG(MF9, DECOUPLEDANIMATIONS, AActor, flags9),
|
||||||
DEFINE_FLAG(MF9, PATHING, AActor, flags9),
|
DEFINE_FLAG(MF9, PATHING, AActor, flags9),
|
||||||
DEFINE_FLAG(MF9, KEEPPATH, AActor, flags9),
|
DEFINE_FLAG(MF9, KEEPPATH, AActor, flags9),
|
||||||
|
DEFINE_FLAG(MF9, NOPATHING, AActor, flags9),
|
||||||
|
|
||||||
// Effect flags
|
// Effect flags
|
||||||
DEFINE_FLAG(FX, VISIBILITYPULSE, AActor, effects),
|
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_FIELD_X(LevelInfo, level_info_t, teamdamage)
|
||||||
|
|
||||||
DEFINE_GLOBAL_NAMED(currentVMLevel, level)
|
DEFINE_GLOBAL_NAMED(currentVMLevel, level)
|
||||||
|
DEFINE_FIELD(FLevelLocals, PathNodes)
|
||||||
DEFINE_FIELD(FLevelLocals, sectors)
|
DEFINE_FIELD(FLevelLocals, sectors)
|
||||||
DEFINE_FIELD(FLevelLocals, lines)
|
DEFINE_FIELD(FLevelLocals, lines)
|
||||||
DEFINE_FIELD(FLevelLocals, sides)
|
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, no_dlg_freeze, LEVEL2_CONV_SINGLE_UNFREEZE)
|
||||||
DEFINE_FIELD_BIT(FLevelLocals, flags2, keepfullinventory, LEVEL2_KEEPFULLINVENTORY)
|
DEFINE_FIELD_BIT(FLevelLocals, flags2, keepfullinventory, LEVEL2_KEEPFULLINVENTORY)
|
||||||
DEFINE_FIELD_BIT(FLevelLocals, flags3, removeitems, LEVEL3_REMOVEITEMS)
|
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, floorplane)
|
||||||
DEFINE_FIELD_X(Sector, sector_t, ceilingplane)
|
DEFINE_FIELD_X(Sector, sector_t, ceilingplane)
|
||||||
|
|
|
@ -44,6 +44,7 @@ DoomEdNums
|
||||||
5065 = InvisibleBridge8
|
5065 = InvisibleBridge8
|
||||||
9001 = MapSpot
|
9001 = MapSpot
|
||||||
9013 = MapSpotGravity
|
9013 = MapSpotGravity
|
||||||
|
9022 = PathNode
|
||||||
9024 = PatrolPoint
|
9024 = PatrolPoint
|
||||||
9025 = SecurityCamera
|
9025 = SecurityCamera
|
||||||
9026 = Spark
|
9026 = Spark
|
||||||
|
@ -285,7 +286,6 @@ DoomEdNums
|
||||||
14163 = MusicChanger, 63
|
14163 = MusicChanger, 63
|
||||||
14164 = MusicChanger, 64
|
14164 = MusicChanger, 64
|
||||||
14165 = MusicChanger
|
14165 = MusicChanger
|
||||||
14166 = PathNode
|
|
||||||
32000 = DoomBuilderCamera
|
32000 = DoomBuilderCamera
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -664,7 +664,7 @@ class Actor : Thinker native
|
||||||
// called before and after triggering a teleporter
|
// called before and after triggering a teleporter
|
||||||
// return false in PreTeleport() to cancel the action early
|
// return false in PreTeleport() to cancel the action early
|
||||||
virtual bool PreTeleport( Vector3 destpos, double destangle, int flags ) { return true; }
|
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 virtual bool OkayToSwitchTarget(Actor other);
|
||||||
native clearscope static class<Actor> GetReplacement(class<Actor> cls);
|
native clearscope static class<Actor> GetReplacement(class<Actor> cls);
|
||||||
|
@ -706,7 +706,6 @@ class Actor : Thinker native
|
||||||
native void ClearFOVInterpolation();
|
native void ClearFOVInterpolation();
|
||||||
native clearscope Vector3 PosRelative(sector sec) const;
|
native clearscope Vector3 PosRelative(sector sec) const;
|
||||||
native void RailAttack(FRailParams p);
|
native void RailAttack(FRailParams p);
|
||||||
native void ClearPath();
|
|
||||||
|
|
||||||
native void HandleSpawnFlags();
|
native void HandleSpawnFlags();
|
||||||
native void ExplodeMissile(line lin = null, Actor target = null, bool onsky = false);
|
native void ExplodeMissile(line lin = null, Actor target = null, bool onsky = false);
|
||||||
|
@ -799,6 +798,46 @@ class Actor : Thinker native
|
||||||
return true;
|
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 TryMove(vector2 newpos, int dropoff, bool missilecheck = false, FCheckPosition tm = null);
|
||||||
native bool CheckMove(vector2 newpos, int flags = 0, FCheckPosition tm = null);
|
native bool CheckMove(vector2 newpos, int flags = 0, FCheckPosition tm = null);
|
||||||
native void NewChaseDir();
|
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
|
class PathNode : Actor
|
||||||
{
|
{
|
||||||
// For non-connected paths. Stamina will be used to set this. Necessary for tele/portals.
|
|
||||||
private int group;
|
|
||||||
|
|
||||||
Array<PathNode> neighbors;
|
Array<PathNode> neighbors;
|
||||||
|
|
||||||
Default
|
Default
|
||||||
{
|
{
|
||||||
//$Arg0 "TID 1"
|
+NOINTERACTION
|
||||||
//$Arg1 "TID 2"
|
|
||||||
//$Arg2 "TID 3"
|
|
||||||
//$Arg3 "TID 4"
|
|
||||||
//$Arg4 "TID 5"
|
|
||||||
//$Arg0Type 14
|
|
||||||
//$Arg1Type 14
|
|
||||||
//$Arg2Type 14
|
|
||||||
//$Arg3Type 14
|
|
||||||
//$Arg4Type 14
|
|
||||||
+NOBLOCKMAP
|
+NOBLOCKMAP
|
||||||
+INVISIBLE
|
+INVISIBLE
|
||||||
+DONTSPLASH
|
+DONTSPLASH
|
||||||
+NOTONAUTOMAP
|
+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";
|
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.
|
// Args are TIDs. Can be one way to force single directions.
|
||||||
|
@ -295,6 +293,13 @@ class PathNode : Actor
|
||||||
} while (node = PathNode(it.Next()))
|
} while (node = PathNode(it.Next()))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
level.HandlePathNode(self, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
override void OnDestroy()
|
||||||
|
{
|
||||||
|
level.HandlePathNode(self, false);
|
||||||
|
Super.OnDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
// For ACS access with ScriptCall.
|
// For ACS access with ScriptCall.
|
||||||
|
|
|
@ -408,7 +408,7 @@ struct LevelLocals native
|
||||||
|
|
||||||
const CLUSTER_HUB = 0x00000001; // Cluster uses hub behavior
|
const CLUSTER_HUB = 0x00000001; // Cluster uses hub behavior
|
||||||
|
|
||||||
|
native readonly Array<PathNode> PathNodes;
|
||||||
native Array<@Sector> Sectors;
|
native Array<@Sector> Sectors;
|
||||||
native Array<@Line> Lines;
|
native Array<@Line> Lines;
|
||||||
native Array<@Side> Sides;
|
native Array<@Side> Sides;
|
||||||
|
@ -476,6 +476,7 @@ struct LevelLocals native
|
||||||
native readonly int compatflags;
|
native readonly int compatflags;
|
||||||
native readonly int compatflags2;
|
native readonly int compatflags2;
|
||||||
native readonly LevelInfo info;
|
native readonly LevelInfo info;
|
||||||
|
native readonly bool pathing;
|
||||||
|
|
||||||
native String GetUDMFString(int type, int index, Name key);
|
native String GetUDMFString(int type, int index, Name key);
|
||||||
native int GetUDMFInt(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 void SpawnParticle(FSpawnParticleParams p);
|
||||||
native VisualThinker SpawnVisualThinker(Class<VisualThinker> type);
|
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.
|
// a few values of this need to be readable by the play code.
|
||||||
|
|
Loading…
Reference in a new issue