diff --git a/Adventures in Lestoria/Adventures in Lestoria.vcxproj b/Adventures in Lestoria/Adventures in Lestoria.vcxproj index c8932fa2..5f27fa3f 100644 --- a/Adventures in Lestoria/Adventures in Lestoria.vcxproj +++ b/Adventures in Lestoria/Adventures in Lestoria.vcxproj @@ -303,7 +303,6 @@ - diff --git a/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters b/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters index dce5a6f2..5a62a907 100644 --- a/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters +++ b/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters @@ -405,9 +405,6 @@ Header Files - - Header Files - Header Files diff --git a/Adventures in Lestoria/Monster.cpp b/Adventures in Lestoria/Monster.cpp index d98ef8bf..db6f1f65 100644 --- a/Adventures in Lestoria/Monster.cpp +++ b/Adventures in Lestoria/Monster.cpp @@ -234,6 +234,18 @@ void Monster::Draw(){ game->view.DrawDecal(GetPos()-vf2d{3,3}*shadowScale/2+vf2d{0,6*GetSizeMult()},GFX["circle.png"].Decal(),shadowScale,BLACK); } game->view.DrawPartialRotatedDecal(GetPos()-vf2d{0,GetZ()},GetFrame().GetSourceImage()->Decal(),0,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,vf2d(GetSizeMult()*(GetFacingDirection()==RIGHT?-1:1),GetSizeMult()),GetBuffs(BuffType::SLOWDOWN).size()>0?Pixel{uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(128+127*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration)))}:WHITE); + + for(size_t counter=0;Pathfinding::sPoint2D&point:path.points){ + Pixel col=CYAN; + if(counterpathIndex+1){ + col=YELLOW; + } + game->view.FillRectDecal(point.pos*float(game->GetCurrentMapData().tilewidth),{3,3},col); + counter++; + } } void Monster::DrawReflection(float drawRatioX,float multiplierX){ game->SetDecalMode(DecalMode::ADDITIVE); @@ -391,18 +403,18 @@ void Monster::AddBuff(BuffType type,float duration,float intensity){ void Monster::StartPathfinding(float pathingTime){ SetState(State::PATH_AROUND); - path=game->pathfinder.Solve_AStar(pos,target,12,OnUpperLevel()); - if(path.size()>0){ - pathIndex=0; + path=game->pathfinder.Solve_WalkPath(pos,target,12,OnUpperLevel()); + if(path.points.size()>0){ + pathIndex=0.f; //We gives this mob 5 seconds to figure out a path to the target. targetAcquireTimer=pathingTime; } } void Monster::PathAroundBehavior(float fElapsedTime){ - if(path.size()>0){ + if(path.points.size()>0){ //Move towards the new path. - geom2d::line moveTowardsLine=geom2d::line(pos,path[pathIndex]*float(game->GetCurrentMapData().tilewidth)); + geom2d::line moveTowardsLine=geom2d::line(pos,path.GetSplinePoint(pathIndex).pos*float(game->GetCurrentMapData().tilewidth)); if(moveTowardsLine.length()>2){ SetPos(pos+moveTowardsLine.vector().norm()*100*fElapsedTime*GetMoveSpdMult()); if(moveTowardsLine.vector().x>0){ @@ -411,11 +423,11 @@ void Monster::PathAroundBehavior(float fElapsedTime){ facingDirection=LEFT; } }else{ - if(size_t(pathIndex+1)>=path.size()){ + if(size_t(pathIndex+1)>=path.points.size()){ //We have reached the end of the path! targetAcquireTimer=0; }else{ - pathIndex++; + pathIndex+=0.2f; } } } else { diff --git a/Adventures in Lestoria/Monster.h b/Adventures in Lestoria/Monster.h index 3271cbb9..452a7f9a 100644 --- a/Adventures in Lestoria/Monster.h +++ b/Adventures in Lestoria/Monster.h @@ -45,6 +45,7 @@ All rights reserved. #include "Attributable.h" #include "Item.h" #include "safemap.h" +#include "Pathfinding.h" INCLUDE_ITEM_DATA @@ -197,8 +198,8 @@ private: bool canMove=true; //Set to false when stuck due to collisions. bool upperLevel=false; vf2d pathTarget={}; - std::vectorpath; - int pathIndex=0; + Pathfinding::sSpline path; + float pathIndex=0; float lastHitTimer=0; std::shared_ptrdamageNumberPtr; int phase=0; diff --git a/Adventures in Lestoria/Pathfinding.cpp b/Adventures in Lestoria/Pathfinding.cpp index 907b10cd..9919e167 100644 --- a/Adventures in Lestoria/Pathfinding.cpp +++ b/Adventures in Lestoria/Pathfinding.cpp @@ -42,55 +42,62 @@ All rights reserved. INCLUDE_game void Pathfinding::Initialize(){ - if(nodes!=nullptr){ - delete[] nodes; - } - nodes = NEW sNode[game->GetCurrentMapData().width * game->GetCurrentMapData().height]; - for (int x = 0; x < game->GetCurrentMapData().width; x++) - for (int y = 0; y < game->GetCurrentMapData().height; y++) - { - nodes[y * game->GetCurrentMapData().width + x].x = x; // ...because we give each node its own coordinates - nodes[y * game->GetCurrentMapData().width + x].y = y; - geom2d::recttile=game->GetTileCollision(game->GetCurrentLevel(),{float(x*game->GetCurrentMapData().tilewidth),float(y*game->GetCurrentMapData().tilewidth)}); - nodes[y * game->GetCurrentMapData().width + x].bObstacle = tile.pos!=game->NO_COLLISION.pos||tile.size!=game->NO_COLLISION.size; - tile=game->GetTileCollision(game->GetCurrentLevel(),{float(x*game->GetCurrentMapData().tilewidth),float(y*game->GetCurrentMapData().tilewidth)},true); - nodes[y * game->GetCurrentMapData().width + x].bObstacleUpper = tile.pos!=game->NO_COLLISION.pos||tile.size!=game->NO_COLLISION.size; - nodes[y * game->GetCurrentMapData().width + x].parent = nullptr; - nodes[y * game->GetCurrentMapData().width + x].bVisited = false; - } - - for (int x = 0; x < game->GetCurrentMapData().width; x++) - for (int y = 0; y < game->GetCurrentMapData().height; y++) - { - if(y>0) - nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y - 1) * game->GetCurrentMapData().width + (x + 0)]); - if(yGetCurrentMapData().height-1) - nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y + 1) * game->GetCurrentMapData().width + (x + 0)]); - if (x>0) - nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y + 0) * game->GetCurrentMapData().width + (x - 1)]); - if(xGetCurrentMapData().width-1) - nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y + 0) * game->GetCurrentMapData().width + (x + 1)]); - /*if (y>0 && x>0) - nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y - 1) * game->GetCurrentMapData().width + (x - 1)]); - if (yGetCurrentMapData().height-1 && x>0) - nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y + 1) * game->GetCurrentMapData().width + (x - 1)]); - if (y>0 && xGetCurrentMapData().width-1) - nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y - 1) * game->GetCurrentMapData().width + (x + 1)]); - if (yGetCurrentMapData().height - 1 && xGetCurrentMapData().width-1) - nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y + 1) * game->GetCurrentMapData().width + (x + 1)]);*/ - } - - // Manually position the start and end markers so they are not nullptr - nodeStart = &nodes[(game->GetCurrentMapData().height / 2) * game->GetCurrentMapData().width + 1]; - nodeEnd = &nodes[(game->GetCurrentMapData().height / 2) * game->GetCurrentMapData().width + game->GetCurrentMapData().width-2]; + nodes.clear(); + sNode*lastNodeAdded=nullptr; + for (int x = 0; x < game->GetCurrentMapData().width; x++) + for (int y = 0; y < game->GetCurrentMapData().height; y++) + { + nodes.insert({x,y}); + sNode node=*nodes.find({x,y}); + node.x = x; // ...because we give each node its own coordinates + node.y = y; // ...because we give each node its own coordinates + geom2d::recttile=game->GetTileCollision(game->GetCurrentLevel(),{float(x*game->GetCurrentMapData().tilewidth),float(y*game->GetCurrentMapData().tilewidth)}); + node.bObstacle = tile.pos!=game->NO_COLLISION.pos||tile.size!=game->NO_COLLISION.size; + tile=game->GetTileCollision(game->GetCurrentLevel(),{float(x*game->GetCurrentMapData().tilewidth),float(y*game->GetCurrentMapData().tilewidth)},true); + node.bObstacleUpper = tile.pos!=game->NO_COLLISION.pos||tile.size!=game->NO_COLLISION.size; + node.parent = nullptr; + node.bVisited = false; + if(nodes.size()==1){//This is the first node added, set as the start node. + nodeStart=&node; + } + nodeEnd=&node; + } + + for (int x = 0; x < game->GetCurrentMapData().width; x++) + for (int y = 0; y < game->GetCurrentMapData().height; y++) + { + sNode&node=const_cast(*nodes.find({x,y})); + if(y>0){ + if(nodes.find({x,y-1})!=nodes.end()){ + node.vecNeighbours.push_back(&*nodes.find({x,y-1})); + } + } + if(yGetCurrentMapData().height-1){ + if(nodes.find({x,y+1})!=nodes.end()){ + node.vecNeighbours.push_back(&*nodes.find({x,y+1})); + } + } + if(x>0){ + if(nodes.find({x-1,y})!=nodes.end()){ + node.vecNeighbours.push_back(&*nodes.find({x-1,y})); + } + } + if(xGetCurrentMapData().width-1){ + if(nodes.find({x+1,y})!=nodes.end()){ + node.vecNeighbours.push_back(&*nodes.find({x+1,y})); + } + } + } } std::vector Pathfinding::Solve_AStar(vf2d startPos,vf2d endPos,float maxRange,bool upperLevel){ float dist=float(sqrt(pow(endPos.x-startPos.x,2)+pow(endPos.y-startPos.y,2))); if(dist>maxRange*game->GetCurrentMapData().tilewidth)return {}; + if(nodes.find(sNode{int(startPos.x),int(startPos.y)})==nodes.end())return{}; + if(nodes.find(sNode{int(endPos.x),int(endPos.y)})==nodes.end())return{}; - nodeStart=&nodes[int(startPos.y/game->GetCurrentMapData().tilewidth)*game->GetCurrentMapData().width+int(startPos.x/game->GetCurrentMapData().tilewidth)]; - nodeEnd=&nodes[int(endPos.y/game->GetCurrentMapData().tilewidth)*game->GetCurrentMapData().width+int(endPos.x/game->GetCurrentMapData().tilewidth)]; + nodeStart=const_cast(&*nodes.find(sNode{int(startPos.x),int(startPos.y)})); + nodeEnd=const_cast(&*nodes.find(sNode{int(endPos.x),int(endPos.y)})); geom2d::rectposPerimeter{{int(std::min(startPos.x,endPos.x)),int(std::min(startPos.y,endPos.y))},{int(abs(endPos.x-startPos.x)),int(abs(endPos.y-startPos.y))}}; @@ -175,4 +182,126 @@ std::vector Pathfinding::Solve_AStar(vf2d startPos,vf2d endPos,float maxRa } else { return {}; } -} \ No newline at end of file +} + +Pathfinding::sSpline Pathfinding::Solve_WalkPath(vf2d startPos,vf2d endPos,float maxRange,bool upperLevel){ + Pathfinding::sSpline newSpline{}; + newSpline.Initialize(Solve_AStar(startPos,endPos,maxRange,upperLevel)); + return newSpline; +} + +void Pathfinding::sSpline::Initialize(const std::vector&points){ + this->points.clear(); + for(const vf2d&point:points){ + this->points.push_back({point}); + } + fTotalSplineLength=0.f; + for (int i = 0; i < this->points.size(); i++) + { + fTotalSplineLength += (this->points[i].length = CalculateSegmentLength(i, true)); + } +} + +Pathfinding::sPoint2D Pathfinding::sSpline::GetSplinePoint(float t, bool bLooped){ + int p0, p1, p2, p3; + float t1=t; + if (!bLooped) + { + p1 = (int)t1 + 1; + p2 = p1 + 1; + p3 = p2 + 1; + p0 = p1 - 1; + } + else + { + p1 = (int)t1; + p2 = (p1 + 1) % points.size(); + p3 = (p2 + 1) % points.size(); + p0 = p1 >= 1 ? p1 - 1 : points.size() - 1; + } + + t = t - (int)t; + + float tt = t * t; + float ttt = tt * t; + + float q1 = -ttt + 2.0f*tt - t; + float q2 = 3.0f*ttt - 5.0f*tt + 2.0f; + float q3 = -3.0f*ttt + 4.0f*tt + t; + float q4 = ttt - tt; + + float tx = 0.5f * (points[p0].pos.x * q1 + points[p1].pos.x * q2 + points[p2].pos.x * q3 + points[p3].pos.x * q4); + float ty = 0.5f * (points[p0].pos.y * q1 + points[p1].pos.y * q2 + points[p2].pos.y * q3 + points[p3].pos.y * q4); + + return{ {tx, ty} }; +} + +Pathfinding::sPoint2D Pathfinding::sSpline::GetSplineGradient(float t, bool bLooped){ + int p0, p1, p2, p3; + float t1=t; + if (!bLooped) + { + p1 = (int)t1 + 1; + p2 = p1 + 1; + p3 = p2 + 1; + p0 = p1 - 1; + } + else + { + p1 = (int)t1; + p2 = (p1 + 1) % points.size(); + p3 = (p2 + 1) % points.size(); + p0 = p1 >= 1 ? p1 - 1 : points.size() - 1; + } + + t = t - (int)t; + + float tt = t * t; + float ttt = tt * t; + + float q1 = -3.0f * tt + 4.0f*t - 1; + float q2 = 9.0f*tt - 10.0f*t; + float q3 = -9.0f*tt + 8.0f*t + 1.0f; + float q4 = 3.0f*tt - 2.0f*t; + + float tx = 0.5f * (points[p0].pos.x * q1 + points[p1].pos.x * q2 + points[p2].pos.x * q3 + points[p3].pos.x * q4); + float ty = 0.5f * (points[p0].pos.y * q1 + points[p1].pos.y * q2 + points[p2].pos.y * q3 + points[p3].pos.y * q4); + + return{ {tx, ty} }; +} + +float Pathfinding::sSpline::CalculateSegmentLength(int node, bool bLooped){ + float fLength = 0.0f; + float fStepSize = 0.005; + + sPoint2D old_point, new_point; + old_point = GetSplinePoint((float)node, bLooped); + + for (float t = 0; t < 1.0f; t += fStepSize) + { + new_point = GetSplinePoint(fmod((float)node + t,1.0f), bLooped); + fLength += sqrtf((new_point.pos.x - old_point.pos.x)*(new_point.pos.x - old_point.pos.x) + + (new_point.pos.y - old_point.pos.y)*(new_point.pos.y - old_point.pos.y)); + old_point = new_point; + } + + return fLength; +} + +float Pathfinding::sSpline::GetNormalisedOffset(float p){ + // Which node is the base? + int i = 0; + while (p > points[i].length) + { + p -= points[i].length; + i++; + } + + // The fractional is the offset + return (float)i + (p / points[i].length); +} + +inline bool operator < (const Pathfinding::sNode& lhs, const Pathfinding::sNode& rhs) +{ return lhs.y < rhs.y || (lhs.y == rhs.y && lhs.x < rhs.x); } +inline bool operator > (const Pathfinding::sNode& lhs, const Pathfinding::sNode& rhs) +{ return lhs.y > rhs.y || (lhs.y == rhs.y && lhs.x > rhs.x); } \ No newline at end of file diff --git a/Adventures in Lestoria/Pathfinding.h b/Adventures in Lestoria/Pathfinding.h index 11c861fe..8820e283 100644 --- a/Adventures in Lestoria/Pathfinding.h +++ b/Adventures in Lestoria/Pathfinding.h @@ -41,18 +41,35 @@ All rights reserved. struct Pathfinding{ struct sNode { + int x=0; // Nodes position in 2D space + int y=0; bool bObstacle = false; // Is the node an obstruction? bool bObstacleUpper = false; // Is the node an obstruction on the upper level? bool bVisited = false; // Have we searched this node before? float fGlobalGoal=0; // Distance to goal so far float fLocalGoal=0; // Distance to goal if we took the alternative route - int x=0; // Nodes position in 2D space - int y=0; - std::vector vecNeighbours; // Connections to neighbours + std::vector vecNeighbours; // Connections to neighbours sNode* parent=nullptr; // Node connecting to this node that offers shortest parent }; - sNode *nodes = nullptr; + struct sPoint2D + { + vf2d pos; + float length; + }; + struct sSpline + { + std::vector points; + float fTotalSplineLength = 0.0f; + + void Initialize(const std::vector&points); + sPoint2D GetSplinePoint(float t, bool bLooped = true); + sPoint2D GetSplineGradient(float t, bool bLooped = true); + float CalculateSegmentLength(int node, bool bLooped = true); + float GetNormalisedOffset(float p); + }; + + std::setnodes; sNode *nodeStart = nullptr; sNode *nodeEnd = nullptr; @@ -60,4 +77,5 @@ struct Pathfinding{ void Initialize(); //maxRange in tiles. Returns the path as points. std::vector Solve_AStar(vf2d startPos,vf2d endPos,float maxRange=8,bool upperLevel=false); + sSpline Solve_WalkPath(vf2d startPos,vf2d endPos,float maxRange=8,bool upperLevel=false); }; \ No newline at end of file diff --git a/Adventures in Lestoria/ShootAfar.cpp b/Adventures in Lestoria/ShootAfar.cpp index 680ac7b2..263c9f1b 100644 --- a/Adventures in Lestoria/ShootAfar.cpp +++ b/Adventures in Lestoria/ShootAfar.cpp @@ -109,7 +109,7 @@ void Monster::STRATEGY::SHOOT_AFAR(Monster&m,float fElapsedTime,std::string stra if(!pathfindingDecision&&m.targetAcquireTimer==0){ m.StartPathfinding(2.5); }else - if((m.path.size()==0&&!m.canMove)||line.length()>=24.f*ConfigInt("Range")/100.f){ + if((m.path.points.size()==0&&!m.canMove)||line.length()>=24.f*ConfigInt("Range")/100.f){ m.SetState(State::NORMAL); } if(moveTowardsLine.vector().x>0){ diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h index ca94b38f..a91434e7 100644 --- a/Adventures in Lestoria/Version.h +++ b/Adventures in Lestoria/Version.h @@ -39,7 +39,7 @@ All rights reserved. #define VERSION_MAJOR 0 #define VERSION_MINOR 2 #define VERSION_PATCH 1 -#define VERSION_BUILD 5574 +#define VERSION_BUILD 5582 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/x64/Release/Adventures in Lestoria.exe b/x64/Release/Adventures in Lestoria.exe index eeb5050e..9c1efbbb 100644 Binary files a/x64/Release/Adventures in Lestoria.exe and b/x64/Release/Adventures in Lestoria.exe differ