summary refs log tree commit diff stats
path: root/src/behaviour_system.cpp
diff options
context:
space:
mode:
authorKelly Rauchenberger <fefferburbia@gmail.com>2021-02-27 17:40:26 -0500
committerKelly Rauchenberger <fefferburbia@gmail.com>2021-02-27 17:40:26 -0500
commite03683852cac9b31ca846fcf13ff53abf99232c7 (patch)
tree4f18e4f6d033547f8bf9210ff8466406ed6dbd49 /src/behaviour_system.cpp
parent4be70b7d55493cdc2d5e909d5101e70a16bee6f1 (diff)
downloadtanetane-e03683852cac9b31ca846fcf13ff53abf99232c7.tar.gz
tanetane-e03683852cac9b31ca846fcf13ff53abf99232c7.tar.bz2
tanetane-e03683852cac9b31ca846fcf13ff53abf99232c7.zip
Added A* pathfinding
Diffstat (limited to 'src/behaviour_system.cpp')
-rw-r--r--src/behaviour_system.cpp171
1 files changed, 170 insertions, 1 deletions
diff --git a/src/behaviour_system.cpp b/src/behaviour_system.cpp index 2d46205..3637b3e 100644 --- a/src/behaviour_system.cpp +++ b/src/behaviour_system.cpp
@@ -1,8 +1,16 @@
1#include "behaviour_system.h" 1#include "behaviour_system.h"
2#include <queue>
3#include <vector>
4#include <utility>
2#include <random> 5#include <random>
3#include "game.h" 6#include "game.h"
4#include "character_system.h" 7#include "character_system.h"
5#include "direction.h" 8#include "direction.h"
9#include "transform_system.h"
10
11bool pathfindingOptionsContains(PathfindingOptions options, PathfindingOptions value) {
12 return (static_cast<int>(options) & static_cast<int>(value)) != 0;
13}
6 14
7void BehaviourSystem::tick(double dt) { 15void BehaviourSystem::tick(double dt) {
8 if (game_.isGameplayPaused()) return; 16 if (game_.isGameplayPaused()) return;
@@ -11,7 +19,7 @@ void BehaviourSystem::tick(double dt) {
11 while (timer_.step()) { 19 while (timer_.step()) {
12 for (int spriteId : game_.getSprites()) { 20 for (int spriteId : game_.getSprites()) {
13 Sprite& sprite = game_.getSprite(spriteId); 21 Sprite& sprite = game_.getSprite(spriteId);
14 if (sprite.wander && !sprite.paused) { 22 if (!sprite.paused && sprite.behaviourType == BehaviourType::Wander) {
15 // 75% chance of changing what's happening 23 // 75% chance of changing what's happening
16 if (std::bernoulli_distribution(0.75)(game_.getRng())) { 24 if (std::bernoulli_distribution(0.75)(game_.getRng())) {
17 // 50% chance of choosing a direction or stopping 25 // 50% chance of choosing a direction or stopping
@@ -31,4 +39,165 @@ void BehaviourSystem::tick(double dt) {
31 } 39 }
32 } 40 }
33 } 41 }
42
43 for (int spriteId : game_.getSprites()) {
44 Sprite& sprite = game_.getSprite(spriteId);
45 if (!sprite.paused && sprite.behaviourType == BehaviourType::Path) {
46 while (!sprite.path.empty() && sprite.path.front().endpoint == sprite.loc) {
47 sprite.path.pop_front();
48 }
49 if (sprite.path.empty()) {
50 game_.getSystem<CharacterSystem>().stopDirecting(spriteId);
51 } else {
52 if (sprite.characterState == CharacterState::Still || sprite.movementDir != sprite.path.front().dir) {
53 game_.getSystem<CharacterSystem>().moveInDirection(spriteId, sprite.path.front().dir);
54 }
55 }
56 }
57 }
58}
59
60void BehaviourSystem::directSpriteToLocation(int spriteId, vec2i pos, PathfindingOptions options) {
61 Sprite& sprite = game_.getSprite(spriteId);
62 sprite.behaviourType = BehaviourType::Path;
63 sprite.pathfindingDestination = pos;
64 sprite.cardinalDirectionsOnly = pathfindingOptionsContains(options, PathfindingOptions::CardinalDirectionsOnly);
65
66 createPath(spriteId);
67}
68
69bool BehaviourSystem::isFollowingPath(int spriteId) {
70 Sprite& sprite = game_.getSprite(spriteId);
71 return sprite.behaviourType == BehaviourType::Path && !sprite.path.empty();
72}
73
74struct PathNodeInfo {
75 int cheapestPathCost = INT_MAX;
76 int estimatedRemainingCost = INT_MAX;
77 vec2i previousPoint;
78 Direction dirFromPreviousPoint;
79};
80
81struct SearchNode {
82 vec2i point;
83 int estimatedCost = INT_MAX;
84 int tiebreaker; // this actually counts downward. wow
85
86 SearchNode(vec2i point, int estimatedCost) : point(point), estimatedCost(estimatedCost) {
87 static int tiebreakerCounter = 0;
88 tiebreaker = tiebreakerCounter--;
89 }
90
91 bool operator>(const SearchNode& rhs) const {
92 return std::tie(estimatedCost, tiebreaker) > std::tie(rhs.estimatedCost, rhs.tiebreaker);
93 }
94};
95
96int estimateRemainingCost(vec2i current, vec2i dest, int movementSpeed, bool cardinalDirectionsOnly) {
97 vec2i difference = dest - current;
98 if (cardinalDirectionsOnly) {
99 return (std::abs(difference.x()) + std::abs(difference.y())) / movementSpeed;
100 } else {
101 return std::max(std::abs(difference.x()), std::abs(difference.y())) / movementSpeed;
102 }
103}
104
105void BehaviourSystem::createPath(int spriteId) {
106 Sprite& sprite = game_.getSprite(spriteId);
107 sprite.path.clear();
108
109 const Map& map = game_.getMap();
110 vec2i mapBounds = map.getMapSize() * map.getTileSize();
111
112 // If it is not possible to reach the destination because of the parity (if
113 // the movement speed is above 1), then adjust the destination.
114 if (sprite.movementSpeed > 1) {
115 if ((sprite.loc.x() % sprite.movementSpeed) != (sprite.pathfindingDestination.x() % sprite.movementSpeed)) {
116 sprite.loc.x() = (sprite.loc.x() / sprite.movementSpeed) * sprite.movementSpeed + (sprite.pathfindingDestination.x() % sprite.movementSpeed);
117 }
118 if ((sprite.loc.y() % sprite.movementSpeed) != (sprite.pathfindingDestination.y() % sprite.movementSpeed)) {
119 sprite.loc.y() = (sprite.loc.y() / sprite.movementSpeed) * sprite.movementSpeed + (sprite.pathfindingDestination.y() % sprite.movementSpeed);
120 }
121 }
122
123 int initialCostGuess = estimateRemainingCost(sprite.loc, sprite.pathfindingDestination, sprite.movementSpeed, sprite.cardinalDirectionsOnly);
124
125 std::map<vec2i, PathNodeInfo> pathNodes;
126 pathNodes[sprite.loc] = PathNodeInfo{.cheapestPathCost = 0, .estimatedRemainingCost = initialCostGuess};
127
128 std::priority_queue<SearchNode, std::vector<SearchNode>, std::greater<SearchNode>> openSet;
129 openSet.emplace(sprite.loc, initialCostGuess);
130
131 while (!openSet.empty()) {
132 SearchNode searchNode = openSet.top();
133 openSet.pop();
134
135 if (searchNode.point == sprite.pathfindingDestination) {
136 // We're there!
137 break;
138 }
139
140 PathNodeInfo& nodeInfo = pathNodes[searchNode.point];
141 int newCost = nodeInfo.cheapestPathCost + 1;
142
143 static const std::vector<Direction> allDirections = {
144 Direction::down,
145 Direction::down_left,
146 Direction::left,
147 Direction::up_left,
148 Direction::up,
149 Direction::up_right,
150 Direction::right,
151 Direction::down_right };
152
153 static const std::vector<Direction> cardinalDirections = {
154 Direction::down,
155 Direction::left,
156 Direction::up,
157 Direction::right };
158
159 const std::vector<Direction>* directionList = sprite.cardinalDirectionsOnly ? &cardinalDirections : &allDirections;
160 for (Direction dir : *directionList) {
161 vec2i newPos = searchNode.point + unitVecInDirection(dir) * sprite.movementSpeed;
162
163 if (newPos.x() < 0 || newPos.y() < 0 || newPos.x() >= mapBounds.w() || newPos.y() >= mapBounds.h()) {
164 // The path can't go outside the map.
165 continue;
166 }
167
168 PathNodeInfo& neighborInfo = pathNodes[newPos];
169
170 if (neighborInfo.cheapestPathCost <= newCost) {
171 // There is already a faster path through this neighbor.
172 continue;
173 }
174
175 CollisionResult collision = game_.getSystem<TransformSystem>().checkCollision(spriteId, searchNode.point, newPos, dir);
176 if (collision.horiz.blocked || collision.vert.blocked) {
177 // There isn't actually an edge to this neighbor.
178 continue;
179 }
180
181 neighborInfo.cheapestPathCost = newCost;
182 neighborInfo.estimatedRemainingCost = estimateRemainingCost(newPos, sprite.pathfindingDestination, sprite.movementSpeed, sprite.cardinalDirectionsOnly);
183 neighborInfo.previousPoint = searchNode.point;
184 neighborInfo.dirFromPreviousPoint = dir;
185
186 openSet.emplace(newPos, neighborInfo.cheapestPathCost + neighborInfo.estimatedRemainingCost);
187 }
188 }
189
190 if (!pathNodes.count(sprite.pathfindingDestination)) {
191 // There was no path to the destination.
192 return;
193 }
194
195 vec2i curPos = sprite.pathfindingDestination;
196 while (curPos != sprite.loc) {
197 PathNodeInfo& nodeInfo = pathNodes[curPos];
198 if (sprite.path.empty() || sprite.path.front().dir != nodeInfo.dirFromPreviousPoint) {
199 sprite.path.push_front(PathfindingInstruction{.dir = nodeInfo.dirFromPreviousPoint, .endpoint = curPos});
200 }
201 curPos = nodeInfo.previousPoint;
202 }
34} 203}