From b2cb4b0a6d3f5d6473495865d9eca0fcfc094fd9 Mon Sep 17 00:00:00 2001 From: Major Cooke Date: Mon, 12 Feb 2024 18:57:16 -0600 Subject: [PATCH] Begin adding PathNodes. --- src/g_level.cpp | 104 +++++++++++++++++ src/g_levellocals.h | 3 + src/gamedata/r_defs.h | 1 + src/namedef_custom.h | 1 + src/p_setup.cpp | 2 + src/playsim/actor.h | 7 +- src/playsim/p_enemy.cpp | 60 ++++++++-- src/scripting/thingdef_data.cpp | 2 + src/scripting/vmthunks_actors.cpp | 1 + wadsrc/static/mapinfo/common.txt | 1 + wadsrc/static/zscript/actors/actor.zs | 4 +- .../zscript/actors/shared/sharedmisc.zs | 107 ++++++++++++++++++ wadsrc/static/zscript/doombase.zs | 2 + wadsrc/static/zscript/mapdata.zs | 1 + 14 files changed, 286 insertions(+), 10 deletions(-) diff --git a/src/g_level.cpp b/src/g_level.cpp index fcf4906a58..919e1071d8 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -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 * 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(cls->FindSymbol("neighbors", true)); + + assert(var); + assert(var->Type->isDynArray()); + assert(static_cast(var->Type)->ElementType == cls); + + return reinterpret_cast*>(reinterpret_cast(self) + var->Offset); +} + +int AS_BinarySearch(TMap* fScore, bool exact = false) +{ + return 0; +} + +void AS_ReconstructPath(TMap* 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(startnode)); + return true; + } + } + + // Begin A* here. + TArray openSet; + TMap cameFrom; + TMap gScore; + TMap 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); +} diff --git a/src/g_levellocals.h b/src/g_levellocals.h index cf63f81b2d..b75e3ef0d2 100644 --- a/src/g_levellocals.h +++ b/src/g_levellocals.h @@ -444,6 +444,9 @@ public: void SetMusic(); + bool AStar(AActor *chaser, AActor *target, AActor *startnode = nullptr, AActor *goalnode = nullptr); + + TArray PathNodes; TArray vertexes; TArray sectors; TArray extsectors; // container for non-trivial sector information. sector_t must be trivially copyable for *_fakeflat to work as intended. diff --git a/src/gamedata/r_defs.h b/src/gamedata/r_defs.h index 3333a0c7a8..3a9c20a646 100644 --- a/src/gamedata/r_defs.h +++ b/src/gamedata/r_defs.h @@ -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 diff --git a/src/namedef_custom.h b/src/namedef_custom.h index 2da881531d..1bb07ad603 100644 --- a/src/namedef_custom.h +++ b/src/namedef_custom.h @@ -202,6 +202,7 @@ xx(Cast) // 'damage type' for the cast call xx(MapSpot) xx(PatrolPoint) xx(PatrolSpecial) +xx(PathNode) xx(Communicator) xx(PowerScanner) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 02f89c860d..051fac4375 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -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); } } diff --git a/src/playsim/actor.h b/src/playsim/actor.h index 845e532820..84af5e50d1 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -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 boneComponentData; // interaction info + TArray > Path; FBlockNode *BlockNode; // links in blocks (if needed) struct sector_t *Sector; subsector_t * subsector; diff --git a/src/playsim/p_enemy.cpp b/src/playsim/p_enemy.cpp index b5bf36133a..242680b962 100644 --- a/src/playsim/p_enemy.cpp +++ b/src/playsim/p_enemy.cpp @@ -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; @@ -2493,9 +2514,31 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi actor->flags7 &= ~MF7_INCHASE; 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; diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index 997ab78b1a..363a8ebb32 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -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), diff --git a/src/scripting/vmthunks_actors.cpp b/src/scripting/vmthunks_actors.cpp index 6c52ef3c27..219d63e0d0 100644 --- a/src/scripting/vmthunks_actors.cpp +++ b/src/scripting/vmthunks_actors.cpp @@ -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); diff --git a/wadsrc/static/mapinfo/common.txt b/wadsrc/static/mapinfo/common.txt index f079ee6e6e..83482b8b66 100644 --- a/wadsrc/static/mapinfo/common.txt +++ b/wadsrc/static/mapinfo/common.txt @@ -285,6 +285,7 @@ DoomEdNums 14163 = MusicChanger, 63 14164 = MusicChanger, 64 14165 = MusicChanger + 14166 = PathNode 32000 = DoomBuilderCamera } diff --git a/wadsrc/static/zscript/actors/actor.zs b/wadsrc/static/zscript/actors/actor.zs index b35c8cd8e6..ff6c49f1d3 100644 --- a/wadsrc/static/zscript/actors/actor.zs +++ b/wadsrc/static/zscript/actors/actor.zs @@ -261,6 +261,7 @@ class Actor : Thinker native private native int InventoryID; // internal counter. native uint freezetics; native Vector2 AutomapOffsets; + native Array 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); diff --git a/wadsrc/static/zscript/actors/shared/sharedmisc.zs b/wadsrc/static/zscript/actors/shared/sharedmisc.zs index 5e146f6c9a..e297651156 100644 --- a/wadsrc/static/zscript/actors/shared/sharedmisc.zs +++ b/wadsrc/static/zscript/actors/shared/sharedmisc.zs @@ -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 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 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)); + } +} \ No newline at end of file diff --git a/wadsrc/static/zscript/doombase.zs b/wadsrc/static/zscript/doombase.zs index 829e752707..a953b4c5b8 100644 --- a/wadsrc/static/zscript/doombase.zs +++ b/wadsrc/static/zscript/doombase.zs @@ -553,6 +553,8 @@ struct LevelLocals native native void SpawnParticle(FSpawnParticleParams p); native VisualThinker SpawnVisualThinker(Class 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. diff --git a/wadsrc/static/zscript/mapdata.zs b/wadsrc/static/zscript/mapdata.zs index db217e7cc7..dd1d406e82 100644 --- a/wadsrc/static/zscript/mapdata.zs +++ b/wadsrc/static/zscript/mapdata.zs @@ -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;