'/>
summary refs log blame commit diff stats
path: root/src/behaviour_system.cpp
blob: ce980f6600b88c131fdba4692097bef8cbf68363 (plain) (tree)
1
2
3
4
5
6
7
8
                             


                  



                             




                                                                                       

                                       

                                       



                                                 















                                                                                
             
           


                                                                                                                      



         
 














                                                                                                         


















                                                                                                              
                           
































































































                                                                                                                                                   



                                                                                                              














                                                                                                                             
                              

























                                                                                                                                                              
 
#include "behaviour_system.h"
#include <queue>
#include <vector>
#include <utility>
#include <random>
#include "game.h"
#include "character_system.h"
#include "direction.h"
#include "transform_system.h"

bool pathfindingOptionsContains(PathfindingOptions options, PathfindingOptions value) {
  return (static_cast<int>(options) & static_cast<int>(value)) != 0;
}

void BehaviourSystem::tick(double dt) {
  if (game_.isGameplayPaused()) return;

  timer_.accumulate(dt);
  while (timer_.step()) {
    for (int spriteId : game_.getSprites()) {
      Sprite& sprite = game_.getSprite(spriteId);
      if (!sprite.paused) {
        if (sprite.behaviourType == BehaviourType::Wander) {
          // 75% chance of changing what's happening
          if (std::bernoulli_distribution(0.75)(game_.getRng())) {
            // 50% chance of choosing a direction or stopping
            if (std::bernoulli_distribution(0.5)(game_.getRng())) {
              Direction dir;
              switch (std::uniform_int_distribution(0,3)(game_.getRng())) {
                case 0: dir = Direction::left; break;
                case 1: dir = Direction::up; break;
                case 2: dir = Direction::right; break;
                default: dir = Direction::down; break;
              }
              game_.getSystem<CharacterSystem>().moveInDirection(spriteId, dir);
            } else {
              game_.getSystem<CharacterSystem>().stopDirecting(spriteId);
            }
          }
        } else if (sprite.behaviourType == BehaviourType::Follow) {
          Sprite& target = game_.getSprite(sprite.followSpriteId);
          game_.getSystem<CharacterSystem>().moveInDirection(spriteId, directionFacingPoint(target.loc - sprite.loc));
        }
      }
    }
  }

  overcorrectionTimer_.accumulate(dt);
  while (overcorrectionTimer_.step()) {
    for (int spriteId : game_.getSprites()) {
      Sprite& sprite = game_.getSprite(spriteId);
      if (!sprite.paused &&
          sprite.behaviourType == BehaviourType::Path &&
          !sprite.path.empty() &&
          sprite.loc != sprite.path.front().endpoint) {
        if (directionFacingPoint(sprite.path.front().endpoint - sprite.loc) != sprite.path.front().dir) {
          game_.getSystem<TransformSystem>().moveSprite(spriteId, sprite.path.front().endpoint);
        }
      }
    }
  }

  for (int spriteId : game_.getSprites()) {
    Sprite& sprite = game_.getSprite(spriteId);
    if (!sprite.paused && sprite.behaviourType == BehaviourType::Path) {
      while (!sprite.path.empty() && sprite.path.front().endpoint == sprite.loc) {
        sprite.path.pop_front();
      }
      if (sprite.path.empty()) {
        game_.getSystem<CharacterSystem>().stopDirecting(spriteId);
      } else {
        if (sprite.characterState == CharacterState::Still || sprite.movementDir != sprite.path.front().dir) {
          game_.getSystem<CharacterSystem>().moveInDirection(spriteId, sprite.path.front().dir);
        }
      }
    }
  }
}

void BehaviourSystem::directSpriteToLocation(int spriteId, vec2i pos, PathfindingOptions options) {
  Sprite& sprite = game_.getSprite(spriteId);
  sprite.orientable = true;
  sprite.behaviourType = BehaviourType::Path;
  sprite.pathfindingDestination = pos;
  sprite.cardinalDirectionsOnly = pathfindingOptionsContains(options, PathfindingOptions::CardinalDirectionsOnly);

  createPath(spriteId);
}

bool BehaviourSystem::isFollowingPath(int spriteId) {
  Sprite& sprite = game_.getSprite(spriteId);
  return sprite.behaviourType == BehaviourType::Path && !sprite.path.empty();
}

struct PathNodeInfo {
  int cheapestPathCost = INT_MAX;
  int estimatedRemainingCost = INT_MAX;
  vec2i previousPoint;
  Direction dirFromPreviousPoint;
};

struct SearchNode {
  vec2i point;
  int estimatedCost = INT_MAX;
  int tiebreaker; // this actually counts downward. wow

  SearchNode(vec2i point, int estimatedCost) : point(point), estimatedCost(estimatedCost) {
    static int tiebreakerCounter = 0;
    tiebreaker = tiebreakerCounter--;
  }

  bool operator>(const SearchNode& rhs) const {
    return std::tie(estimatedCost, tiebreaker) > std::tie(rhs.estimatedCost, rhs.tiebreaker);
  }
};

int estimateRemainingCost(vec2i current, vec2i dest, int movementSpeed, bool cardinalDirectionsOnly) {
  vec2i difference = dest - current;
  if (cardinalDirectionsOnly) {
    return (std::abs(difference.x()) + std::abs(difference.y())) / movementSpeed;
  } else {
    return std::max(std::abs(difference.x()), std::abs(difference.y())) / movementSpeed;
  }
}

void BehaviourSystem::createPath(int spriteId) {
  Sprite& sprite = game_.getSprite(spriteId);
  sprite.path.clear();

  const Map& map = game_.getMap();
  vec2i mapBounds = map.getMapSize() * map.getTileSize();

  // If it is not possible to reach the destination because of the parity (if
  // the movement speed is above 1), then adjust the destination.
  if (sprite.movementSpeed > 1) {
    if ((sprite.loc.x() % sprite.movementSpeed) != (sprite.pathfindingDestination.x() % sprite.movementSpeed)) {
      sprite.loc.x() = (sprite.loc.x() / sprite.movementSpeed) * sprite.movementSpeed + (sprite.pathfindingDestination.x() % sprite.movementSpeed);
    }
    if ((sprite.loc.y() % sprite.movementSpeed) != (sprite.pathfindingDestination.y() % sprite.movementSpeed)) {
      sprite.loc.y() = (sprite.loc.y() / sprite.movementSpeed) * sprite.movementSpeed + (sprite.pathfindingDestination.y() % sprite.movementSpeed);
    }
  }

  int initialCostGuess = estimateRemainingCost(sprite.loc, sprite.pathfindingDestination, sprite.movementSpeed, sprite.cardinalDirectionsOnly);

  std::map<vec2i, PathNodeInfo> pathNodes;
  pathNodes[sprite.loc] = PathNodeInfo{.cheapestPathCost = 0, .estimatedRemainingCost = initialCostGuess};

  std::priority_queue<SearchNode, std::vector<SearchNode>, std::greater<SearchNode>> openSet;
  openSet.emplace(sprite.loc, initialCostGuess);

  while (!openSet.empty()) {
    SearchNode searchNode = openSet.top();
    openSet.pop();

    if (searchNode.point == sprite.pathfindingDestination) {
      // We're there!
      break;
    }

    PathNodeInfo& nodeInfo = pathNodes[searchNode.point];
    int newCost = nodeInfo.cheapestPathCost + 1;

    static const std::vector<Direction> allDirections = {
      Direction::down,
      Direction::down_left,
      Direction::left,
      Direction::up_left,
      Direction::up,
      Direction::up_right,
      Direction::right,
      Direction::down_right };

    static const std::vector<Direction> cardinalDirections = {
      Direction::down,
      Direction::left,
      Direction::up,
      Direction::right };

    std::vector<Direction> directionList = sprite.cardinalDirectionsOnly ? cardinalDirections : allDirections;
    directionList.push_back(nodeInfo.dirFromPreviousPoint);

    for (Direction dir : directionList) {
      vec2i newPos = searchNode.point + unitVecInDirection(dir) * sprite.movementSpeed;

      if (newPos.x() < 0 || newPos.y() < 0 || newPos.x() >= mapBounds.w() || newPos.y() >= mapBounds.h()) {
        // The path can't go outside the map.
        continue;
      }

      PathNodeInfo& neighborInfo = pathNodes[newPos];

      if (neighborInfo.cheapestPathCost <= newCost) {
        // There is already a faster path through this neighbor.
        continue;
      }

      CollisionResult collision = game_.getSystem<TransformSystem>().checkCollision(spriteId, searchNode.point, newPos, dir);
      if (collision.blocked) {
        // There isn't actually an edge to this neighbor.
        continue;
      }

      neighborInfo.cheapestPathCost = newCost;
      neighborInfo.estimatedRemainingCost = estimateRemainingCost(newPos, sprite.pathfindingDestination, sprite.movementSpeed, sprite.cardinalDirectionsOnly);
      neighborInfo.previousPoint = searchNode.point;
      neighborInfo.dirFromPreviousPoint = dir;

      openSet.emplace(newPos, neighborInfo.cheapestPathCost + neighborInfo.estimatedRemainingCost);
    }
  }

  if (!pathNodes.count(sprite.pathfindingDestination)) {
    // There was no path to the destination.
    return;
  }

  vec2i curPos = sprite.pathfindingDestination;
  while (curPos != sprite.loc) {
    PathNodeInfo& nodeInfo = pathNodes[curPos];
    if (sprite.path.empty() || sprite.path.front().dir != nodeInfo.dirFromPreviousPoint) {
      sprite.path.push_front(PathfindingInstruction{.dir = nodeInfo.dirFromPreviousPoint, .endpoint = curPos});
    }
    curPos = nodeInfo.previousPoint;
  }
}