Begin adding PathNodes.

This commit is contained in:
Major Cooke 2024-02-12 18:57:16 -06:00 committed by Rachael Alexanderson
parent 3033fafaa7
commit b2cb4b0a6d
14 changed files with 286 additions and 10 deletions

View file

@ -98,6 +98,7 @@
#include "s_music.h"
#include "fragglescript/t_script.h"
#include "texturemanager.h"
void STAT_StartNewGame(const char *lev);
@ -2466,3 +2467,106 @@ DEFINE_ACTION_FUNCTION(FLevelLocals, GetEpisodeName)
ACTION_RETURN_STRING(GStrings.localize(STAT_EpisodeName().GetChars()));
}
//----------------------------------------------------------------------------
// Pathfinding
//----------------------------------------------------------------------------
// Code by RicardoLuis0
TArray<AActor*> * GetNeighbors(AActor * self)
{
PClass * cls = PClass::FindClass("PathNode");
if(!cls->IsAncestorOf(self->GetClass()))
{
ThrowAbortException(X_BAD_SELF, "Invalid class passed to GetNeighbors (must be PathNodeInfo)");
}
PField *var = dyn_cast<PField>(cls->FindSymbol("neighbors", true));
assert(var);
assert(var->Type->isDynArray());
assert(static_cast<PDynArray*>(var->Type)->ElementType == cls);
return reinterpret_cast<TArray<AActor*>*>(reinterpret_cast<uintptr_t>(self) + var->Offset);
}
int AS_BinarySearch(TMap<AActor*, double>* fScore, bool exact = false)
{
return 0;
}
void AS_ReconstructPath(TMap<AActor*, AActor*>* cameFrom, AActor* chaser)
{
}
bool FLevelLocals::AStar(AActor* chaser, AActor* target, AActor* startnode, AActor* goalnode)
{
if (!chaser || !target || PathNodes.Size() < 1)
return false;
// If supplying nodes, skip the search.
const bool getstart = (!startnode || !startnode->IsKindOf(NAME_PathNode));
const bool getgoal = (!goalnode || !goalnode->IsKindOf(NAME_PathNode));
if (getstart || getgoal)
{
double dist[2];
dist[0] = dist[1] = 100000000.0;
for (int i = 0; i < PathNodes.Size(); i++)
{
AActor *node = PathNodes[i];
if (!node) continue;
double dis;
if (getstart)
{
dis = node->Distance2DSquared(chaser);
if (dis < dist[0] && P_CheckSight(node, chaser)) // TO DO: Make 3D aware, so 3D floors can work better.
{
startnode = node;
dist[0] = dis;
}
}
dis = node->Distance2DSquared(target);
if (dis < dist[1] && P_CheckSight(node, target))
{
goalnode = node;
dist[1] = dis;
}
}
// Incomplete graph.
if (!startnode || !goalnode)
return false;
if (startnode == goalnode)
{
chaser->ClearPath();
chaser->Path.Push(MakeObjPtr<AActor*>(startnode));
return true;
}
}
// Begin A* here.
TArray<AActor*> openSet;
TMap<AActor*, AActor*> cameFrom;
TMap<AActor*, double> gScore;
TMap<AActor*, double> fScore;
return false;
}
DEFINE_ACTION_FUNCTION(FLevelLocals, AStar)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
PARAM_OBJECT(chaser, AActor);
PARAM_OBJECT(target, AActor);
PARAM_OBJECT(startnode, AActor);
PARAM_OBJECT(goalnode, AActor);
return self->AStar(chaser, target, startnode, goalnode);
}

View file

@ -444,6 +444,9 @@ public:
void SetMusic();
bool AStar(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.

View file

@ -503,6 +503,7 @@ enum
SECMF_OVERLAPPING = 512, // floor and ceiling overlap and require special renderer action.
SECMF_NOSKYWALLS = 1024, // Do not draw "sky walls"
SECMF_LIFT = 2048, // For MBF monster AI
SECMF_NOPATHING = 4096, // monsters cannot path find in these areas, saves on time and resources
};
enum

View file

@ -202,6 +202,7 @@ xx(Cast) // 'damage type' for the cast call
xx(MapSpot)
xx(PatrolPoint)
xx(PatrolSpecial)
xx(PathNode)
xx(Communicator)
xx(PowerScanner)

View file

@ -611,6 +611,8 @@ void P_SetupLevel(FLevelLocals *Level, int position, bool newGame)
while ((ac = it.Next()))
{
ac->SetDynamicLights();
if (ac->IsKindOf(NAME_PathNode))
Level->PathNodes.Push(ac);
}
}

View file

@ -442,7 +442,9 @@ enum ActorFlag9
MF9_DOSHADOWBLOCK = 0x00000002, // [inkoalawetrust] Should the monster look for SHADOWBLOCK actors ?
MF9_SHADOWBLOCK = 0x00000004, // [inkoalawetrust] Actors in the line of fire with this flag trigger the MF_SHADOW aiming penalty.
MF9_SHADOWAIMVERT = 0x00000008, // [inkoalawetrust] Monster aim is also offset vertically when aiming at shadow actors.
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_KEEPPATH = 0x00000040, // [MC] Forces monsters to keep to the path when target's in sight.
};
// --- mobj.renderflags ---
@ -1101,6 +1103,8 @@ public:
void AttachLight(unsigned int count, const FLightDefaults *lightdef);
void SetDynamicLights();
void ClearPath();
// info for drawing
// NOTE: The first member variable *must* be snext.
AActor *snext, **sprev; // links in sector (if needed)
@ -1157,6 +1161,7 @@ public:
TObjPtr<DBoneComponents*> boneComponentData;
// interaction info
TArray<TObjPtr<AActor*> > Path;
FBlockNode *BlockNode; // links in blocks (if needed)
struct sector_t *Sector;
subsector_t * subsector;

View file

@ -2199,6 +2199,26 @@ DEFINE_ACTION_FUNCTION(AActor, A_ClearLastHeard)
return 0;
}
//==========================================================================
//
// ClearPath
//
//==========================================================================
void AActor::ClearPath()
{
Path.Clear();
if (goal && goal->IsKindOf(NAME_PathNode))
goal = nullptr;
}
DEFINE_ACTION_FUNCTION(AActor, ClearPath)
{
PARAM_SELF_PROLOGUE(AActor);
self->ClearPath();
return 0;
}
//==========================================================================
//
// A_Wander
@ -2411,7 +2431,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi
// [RH] Friendly monsters will consider chasing whoever hurts a player if they
// don't already have a target.
if (actor->flags & MF_FRIENDLY && actor->target == NULL)
if (actor->flags & MF_FRIENDLY && actor->target == nullptr)
{
player_t *player;
@ -2443,7 +2463,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi
}
if (!actor->target || !(actor->target->flags & MF_SHOOTABLE))
{ // look for a new target
if (actor->target != NULL && (actor->target->flags2 & MF2_NONSHOOTABLE))
if (actor->target != nullptr && (actor->target->flags2 & MF2_NONSHOOTABLE))
{
// Target is only temporarily unshootable, so remember it.
actor->lastenemy = actor->target;
@ -2451,17 +2471,17 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi
// hurt our old one temporarily.
actor->threshold = 0;
}
if (P_LookForPlayers (actor, !(flags & CHF_DONTLOOKALLAROUND), NULL) && actor->target != actor->goal)
if (P_LookForPlayers (actor, !(flags & CHF_DONTLOOKALLAROUND), nullptr) && actor->target != actor->goal)
{ // got a new target
actor->flags7 &= ~MF7_INCHASE;
return;
}
if (actor->target == NULL)
if (actor->target == nullptr)
{
if (flags & CHF_DONTIDLE || actor->flags & MF_FRIENDLY)
{
//A_Look(actor);
if (actor->target == NULL)
if (actor->target == nullptr)
{
if (!dontmove) A_Wander(actor);
actor->flags7 &= ~MF7_INCHASE;
@ -2470,6 +2490,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi
}
else
{
actor->ClearPath();
actor->SetIdle();
actor->flags7 &= ~MF7_INCHASE;
return;
@ -2494,8 +2515,30 @@ 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->AStar(actor, actor->target))
actor->goal = actor->Path[0];
}
}
// [RH] Don't attack if just moving toward goal
if (actor->target == actor->goal || (actor->flags5&MF5_CHASEGOAL && actor->goal != NULL))
else if (actor->target == actor->goal || (actor->flags5&MF5_CHASEGOAL && actor->goal != nullptr))
{
AActor * savedtarget = actor->target;
actor->target = actor->goal;
@ -2513,7 +2556,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi
// as the goal.
while ( (spec = specit.Next()) )
{
P_ExecuteSpecial(actor->Level, spec->special, NULL, actor, false, spec->args[0],
P_ExecuteSpecial(actor->Level, spec->special, nullptr, actor, false, spec->args[0],
spec->args[1], spec->args[2], spec->args[3], spec->args[4]);
}
@ -2536,6 +2579,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi
if (newgoal != NULL && delay != 0)
{
actor->flags4 |= MF4_INCOMBAT;
actor->ClearPath(); // [MC] Just to be safe.
actor->SetIdle();
}
actor->flags7 &= ~MF7_INCHASE;

View file

@ -353,6 +353,8 @@ static FFlagDef ActorFlagDefs[]=
DEFINE_FLAG(MF9, SHADOWBLOCK, AActor, flags9),
DEFINE_FLAG(MF9, SHADOWAIMVERT, AActor, flags9),
DEFINE_FLAG(MF9, DECOUPLEDANIMATIONS, AActor, flags9),
DEFINE_FLAG(MF9, PATHING, AActor, flags9),
DEFINE_FLAG(MF9, KEEPPATH, AActor, flags9),
// Effect flags
DEFINE_FLAG(FX, VISIBILITYPULSE, AActor, effects),

View file

@ -2125,6 +2125,7 @@ DEFINE_FIELD(AActor, LightLevel)
DEFINE_FIELD(AActor, ShadowAimFactor)
DEFINE_FIELD(AActor, ShadowPenaltyFactor)
DEFINE_FIELD(AActor, AutomapOffsets)
DEFINE_FIELD(AActor, Path)
DEFINE_FIELD_X(FCheckPosition, FCheckPosition, thing);
DEFINE_FIELD_X(FCheckPosition, FCheckPosition, pos);

View file

@ -285,6 +285,7 @@ DoomEdNums
14163 = MusicChanger, 63
14164 = MusicChanger, 64
14165 = MusicChanger
14166 = PathNode
32000 = DoomBuilderCamera
}

View file

@ -261,6 +261,7 @@ class Actor : Thinker native
private native int InventoryID; // internal counter.
native uint freezetics;
native Vector2 AutomapOffsets;
native Array<Actor> Path; // Cannot be cast to PathNode, unfortunately.
meta String Obituary; // Player was killed by this actor
meta String HitObituary; // Player was killed by this actor in melee
@ -697,7 +698,7 @@ class Actor : Thinker native
native void SoundAlert(Actor target, bool splash = false, double maxdist = 0);
native void ClearBounce();
native TerrainDef GetFloorTerrain();
native bool CheckLocalView(int consoleplayer = -1 /* parameter is not used anymore but needed for backward compatibilityö. */);
native bool CheckLocalView(int consoleplayer = -1 /* parameter is not used anymore but needed for backward compatibility. */);
native bool CheckNoDelay();
native bool UpdateWaterLevel (bool splash = true);
native bool IsZeroDamage();
@ -705,6 +706,7 @@ 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);

View file

@ -245,3 +245,110 @@ class SpeakerIcon : Unknown
Scale 0.125;
}
}
//===============================================================
// Path Nodes
//===============================================================
class PathNode : Actor
{
// For non-connected paths. Stamina will be used to set this. Necessary for tele/portals.
private int group;
Array<Actor> 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
+NOBLOCKMAP
+INVISIBLE
+DONTSPLASH
+NOTONAUTOMAP
+NOGRAVITY // TO DO: Look into 3D variant for traversing up and down 3D floors and floating monsters.
RenderStyle "None";
MeleeRange 2048; // Sight checks limited to this. 0 = infinite.
}
// Args are TIDs. Can be one way to force single directions.
override void PostBeginPlay()
{
Super.PostBeginPlay();
for (int i = 0; i < Args.Size(); i++)
{
if (!Args[i]) continue;
let it = level.CreateActorIterator(Args[i], "PathNode");
PathNode node;
do
{
if (node && node != self)
neighbors.Push(node);
} while (node = PathNode(it.Next()))
}
}
// For ACS access with ScriptCall.
// 'con' values are:
// 0: No connections
// 1: Connect tid1 to tid2 one-way
// 2: ^ but two-way.
static void SetConnectionGlobal(int tid1, int tid2, int con)
{
if (tid1 == 0 || tid2 == 0)
return;
PathNode node;
Array<PathNode> nodes2; // Cache for actors with tid2
{
let it = Level.CreateActorIterator(tid2, "PathNode");
do
{
if (node)
nodes2.Push(node);
} while (node = PathNode(it.Next()))
}
// Nothing to set to.
if (nodes2.Size() < 1)
return;
let it = Level.CreateActorIterator(tid1, "PathNode");
node = null;
do
{
if (node)
{
foreach (n2 : nodes2)
{
if (n2)
{
node.SetConnection(n2, con);
n2.SetConnection(node, (con > 1));
}
}
}
} while (node = PathNode(it.Next()))
}
void SetConnection(PathNode other, bool on)
{
if (!other) return;
if (on)
{
if (neighbors.Find(other) >= neighbors.Size())
neighbors.Push(other);
}
else neighbors.Delete(neighbors.Find(other));
}
}

View file

@ -553,6 +553,8 @@ struct LevelLocals native
native void SpawnParticle(FSpawnParticleParams p);
native VisualThinker SpawnVisualThinker(Class<VisualThinker> type);
native bool AStar(Actor chaser, Actor target, Actor startnode = null, Actor goalnode = null);
}
// a few values of this need to be readable by the play code.

View file

@ -435,6 +435,7 @@ struct Sector native play
SECMF_UNDERWATERMASK = 32+64,
SECMF_DRAWN = 128, // sector has been drawn at least once
SECMF_HIDDEN = 256, // Do not draw on textured automap
SECMF_NOPATHING = 4096, // monsters cannot path find in these areas, saves on time and resources
}
native uint16 MoreFlags;