mirror of
https://github.com/worldforge/cyphesis
synced 2026-08-13 12:26:04 -04:00
We've introduced the properties "speed-ground", "speed-water" and "speed-flight" which specifies the top speed in m/s for each medium. The "propel" attribute is now normalized, since it's multiplied with the applicable top speed (depending on the medium, although currently we only use the ground speed, TODO).
364 lines
13 KiB
C++
364 lines
13 KiB
C++
/*
|
|
Copyright (C) 2014 Erik Ogenvik
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include "Steering.h"
|
|
#include "Awareness.h"
|
|
|
|
#include "rulesets/MemEntity.h"
|
|
|
|
#include "common/debug.h"
|
|
|
|
#include <wfmath/point.h>
|
|
#include <wfmath/vector.h>
|
|
#include <wfmath/rotbox.h>
|
|
#include <wfmath/segment.h>
|
|
|
|
#include <iostream>
|
|
|
|
static const bool debug_flag = true;
|
|
|
|
Steering::Steering(MemEntity& avatar) :
|
|
mAwareness(nullptr),
|
|
mAvatar(avatar),
|
|
mDestinationEntityId(-1),
|
|
mDestinationRadius(1.0),
|
|
mSteeringEnabled(false),
|
|
mUpdateNeeded(false),
|
|
mPadding(16),
|
|
mMaxSpeed(5),
|
|
mDesiredSpeed(0.5),
|
|
mExpectingServerMovement(false),
|
|
mPathResult(0),
|
|
mAvatarHorizRadius(0.4)
|
|
{
|
|
auto speedGroundProp = avatar.getPropertyType<double>("speed-ground");
|
|
if (speedGroundProp) {
|
|
mMaxSpeed = speedGroundProp->data();
|
|
}
|
|
|
|
}
|
|
|
|
void Steering::setAwareness(Awareness* awareness)
|
|
{
|
|
auto& bbox = mAvatar.m_location.bBox();
|
|
if (bbox.isValid()) {
|
|
WFMath::CoordType squareHorizRadius = std::max(square(bbox.lowCorner().x()) +
|
|
square(bbox.lowCorner().z()),
|
|
square(bbox.highCorner().x()) +
|
|
square(bbox.highCorner().z()));
|
|
mAvatarHorizRadius = std::sqrt(squareHorizRadius);
|
|
}
|
|
mAwareness = awareness;
|
|
mTileListenerConnection.disconnect();
|
|
if (mAwareness) {
|
|
mTileListenerConnection = mAwareness->EventTileUpdated.connect(sigc::mem_fun(*this, &Steering::Awareness_TileUpdated));
|
|
setAwarenessArea();
|
|
}
|
|
}
|
|
|
|
void Steering::setDestination(int entityId, const WFMath::Point<3>& entityRelativePosition, float radius, double currentServerTimestamp)
|
|
{
|
|
if (mAwareness) {
|
|
mDestinationEntityId = entityId;
|
|
mEntityRelativeDestination = entityRelativePosition;
|
|
|
|
auto currentAvatarPos = getCurrentAvatarPosition(currentServerTimestamp);
|
|
|
|
WFMath::Point<3> finalPosition = entityRelativePosition;
|
|
updateDestination(currentServerTimestamp, entityId, finalPosition);
|
|
|
|
float distanceAvatarDestination = WFMath::Distance(currentAvatarPos, finalPosition);
|
|
|
|
float finalRadius = radius;
|
|
|
|
//Check if the destination is too far away. If so we should adjust it closer, and increase the radius.
|
|
//This depends on the AI updating the destination at regular intervals.
|
|
if (distanceAvatarDestination > (mAwareness->getTileSizeInMeters() * 10)) {
|
|
WFMath::Vector<3> vector = finalPosition - currentAvatarPos;
|
|
|
|
finalPosition = currentAvatarPos + (vector.normalize() * mAwareness->getTileSizeInMeters() * 10);
|
|
finalRadius = (radius * 10.f);
|
|
}
|
|
|
|
//Only update if destination or radius has changed, or if the current tile of the avatar isn't known.
|
|
if (mViewDestination != finalPosition || mDestinationRadius != finalRadius
|
|
|| !mAwareness->isPositionAware(currentAvatarPos.x(), currentAvatarPos.z())) {
|
|
mViewDestination = finalPosition;
|
|
mDestinationRadius = finalRadius;
|
|
mUpdateNeeded = true;
|
|
|
|
setAwarenessArea();
|
|
mAvatarPositionLastUpdate = currentAvatarPos;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Steering::setAwarenessArea()
|
|
{
|
|
if (mAwareness) {
|
|
if (mViewDestination.isValid()) {
|
|
WFMath::Point<2> destination2d(mViewDestination.x(), mViewDestination.z());
|
|
WFMath::Point<2> entityPosition2d(mAvatar.m_location.m_pos.x(), mAvatar.m_location.m_pos.z());
|
|
|
|
WFMath::Vector<2> direction(destination2d - entityPosition2d);
|
|
auto theta = std::atan2(direction.y(), direction.x()); // rotation about Y
|
|
WFMath::RotMatrix<2> rm;
|
|
rm.rotation(theta);
|
|
|
|
WFMath::Point<2> start = entityPosition2d;
|
|
start -= WFMath::Vector<2>(mPadding, mPadding);
|
|
|
|
WFMath::Vector<2> size(direction.mag() + (mPadding * 2), mPadding * 2);
|
|
|
|
WFMath::RotBox<2> area;
|
|
area.size() = size;
|
|
area.corner0() = start;
|
|
area.orientation() = WFMath::RotMatrix<2>().identity();
|
|
area.rotatePoint(rm, entityPosition2d);
|
|
|
|
mAwareness->setAwarenessArea(mAvatar.getId(), area, WFMath::Segment<2>(entityPosition2d, destination2d));
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t Steering::unawareAreaCount() const {
|
|
if (mAwareness) {
|
|
return mAwareness->unawareTilesInArea(mAvatar.getId());
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
void Steering::setDesiredSpeed(float desiredSpeed)
|
|
{
|
|
mDesiredSpeed = desiredSpeed;
|
|
}
|
|
|
|
int Steering::getPathResult() const
|
|
{
|
|
return mPathResult;
|
|
}
|
|
|
|
void Steering::updateDestination(double currentServerTimestamp, int entityId, WFMath::Point<3>& pos) {
|
|
if (mAwareness && mAvatar.m_location.m_loc) {
|
|
if (entityId != mAvatar.m_location.m_loc->getIntId()) {
|
|
mAwareness->projectPosition(mDestinationEntityId, pos, currentServerTimestamp);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int Steering::updatePath(const WFMath::Point<3>& currentAvatarPosition)
|
|
{
|
|
mPath.clear();
|
|
if (!mAwareness) {
|
|
mPathResult = -7;
|
|
return mPathResult;
|
|
}
|
|
if (!mViewDestination.isValid()) {
|
|
mPathResult = -8;
|
|
return mPathResult;
|
|
}
|
|
mPathResult = mAwareness->findPath(currentAvatarPosition, mViewDestination, mDestinationRadius, mPath);
|
|
//debug_print("Updating path, size of new path: " << result << ". Pos: " << currentAvatarPosition);
|
|
EventPathUpdated();
|
|
mUpdateNeeded = false;
|
|
return mPathResult;
|
|
}
|
|
|
|
int Steering::updatePath(double currentTimestamp)
|
|
{
|
|
if (!mAwareness) {
|
|
return -1;
|
|
}
|
|
auto currentEntityPos = mAvatar.m_location.m_pos;
|
|
if (mAvatar.m_location.m_velocity.isValid()) {
|
|
currentEntityPos += (mAvatar.m_location.m_velocity * (currentTimestamp - mAvatar.m_location.timeStamp()));
|
|
}
|
|
|
|
updateDestination(currentTimestamp, mDestinationEntityId, mViewDestination);
|
|
|
|
return updatePath(currentEntityPos);
|
|
}
|
|
|
|
void Steering::requestUpdate()
|
|
{
|
|
mUpdateNeeded = true;
|
|
}
|
|
|
|
void Steering::startSteering()
|
|
{
|
|
mSteeringEnabled = true;
|
|
mExpectingServerMovement = false;
|
|
}
|
|
|
|
void Steering::stopSteering()
|
|
{
|
|
if (!mSteeringEnabled) {
|
|
return;
|
|
}
|
|
mSteeringEnabled = false;
|
|
mExpectingServerMovement = false;
|
|
mLastSentVelocity = WFMath::Vector<2>();
|
|
|
|
//reset path
|
|
mPath = std::list<WFMath::Point<3>>();
|
|
mPathResult = 0;
|
|
EventPathUpdated();
|
|
|
|
}
|
|
|
|
bool Steering::isEnabled() const
|
|
{
|
|
return mSteeringEnabled;
|
|
}
|
|
|
|
const std::list<WFMath::Point<3>>& Steering::getPath() const
|
|
{
|
|
return mPath;
|
|
}
|
|
|
|
WFMath::Point<3> Steering::getCurrentAvatarPosition(double currentTimestamp)
|
|
{
|
|
auto currentEntityPos = mAvatar.m_location.m_pos;
|
|
if (mAvatar.m_location.m_velocity.isValid()) {
|
|
currentEntityPos += (mAvatar.m_location.m_velocity * (currentTimestamp - mAvatar.m_location.timeStamp()));
|
|
}
|
|
return currentEntityPos;
|
|
}
|
|
|
|
SteeringResult Steering::update(double currentTimestamp)
|
|
{
|
|
SteeringResult result{};
|
|
if (mSteeringEnabled && mAwareness) {
|
|
|
|
auto currentEntityPos = getCurrentAvatarPosition(currentTimestamp);
|
|
|
|
//if (mUpdateNeeded) {
|
|
updateDestination(currentTimestamp, mDestinationEntityId, mViewDestination);
|
|
updatePath(currentEntityPos);
|
|
//}
|
|
if (!mPath.empty()) {
|
|
const auto& finalDestination = mPath.back();
|
|
|
|
const WFMath::Point<2> entityPosition(currentEntityPos.x(), currentEntityPos.z());
|
|
//First check if we've arrived at our actual destination.
|
|
if (WFMath::Distance(WFMath::Point<2>(finalDestination.x(), finalDestination.z()), entityPosition) < mAvatarHorizRadius) {
|
|
//We've arrived at our destination. If we're moving we should stop.
|
|
if (mLastSentVelocity != WFMath::Vector<2>::ZERO()) {
|
|
result.direction = WFMath::Vector<3>::ZERO();
|
|
mLastSentVelocity = WFMath::Vector<2>::ZERO();
|
|
mExpectingServerMovement = true;
|
|
}
|
|
stopSteering();
|
|
} else {
|
|
//We should send a move op if we're either not moving, or we've reached a waypoint, or we need to divert a lot.
|
|
|
|
WFMath::Point<2> nextWaypoint(mPath.front().x(), mPath.front().z());
|
|
while (WFMath::Distance(nextWaypoint, entityPosition) < mAvatarHorizRadius && mPath.size() > 1) {
|
|
mPath.pop_front();
|
|
nextWaypoint = WFMath::Point<2>(mPath.front().x(), mPath.front().z());
|
|
}
|
|
|
|
WFMath::Vector<2> distance = nextWaypoint - entityPosition;
|
|
WFMath::Vector<2> velocity = distance;
|
|
WFMath::Point<2> destination;
|
|
velocity = velocity.normalize() * mDesiredSpeed;
|
|
|
|
result.timeToNextWaypoint = distance.mag() / mMaxSpeed;
|
|
|
|
if (mPath.size() == 1) {
|
|
//if the next waypoint is the destination we should send a "move to position" update to the server, to make sure that we stop when we've arrived.
|
|
//otherwise, if there's too much lag, we might end up overshooting our destination and will have to double back
|
|
destination = nextWaypoint;
|
|
}
|
|
|
|
//Check if we need to divert in order to avoid colliding.
|
|
WFMath::Vector<2> newVelocity;
|
|
bool avoiding = mAwareness->avoidObstacles(mAvatar.getIntId(), entityPosition, velocity * mMaxSpeed, newVelocity, currentTimestamp);
|
|
if (avoiding) {
|
|
auto newMag = newVelocity.mag();
|
|
auto relativeMag = mMaxSpeed / newMag;
|
|
|
|
velocity = newVelocity;
|
|
velocity.normalize();
|
|
velocity *= relativeMag;
|
|
|
|
//Schedule a new steering op very soon
|
|
result.timeToNextWaypoint = 0.2f;
|
|
mUpdateNeeded = true;
|
|
}
|
|
|
|
bool shouldSend = false;
|
|
if (velocity.isValid()) {
|
|
if (mLastSentVelocity.isValid()) {
|
|
//If the entity has stopped, and we're not waiting for confirmation to a movement request we've made, we need to start moving.
|
|
if (mAvatar.m_location.velocity() == WFMath::Vector<3>::ZERO() && !mExpectingServerMovement) {
|
|
shouldSend = true;
|
|
} else {
|
|
auto currentTheta = std::atan2(mLastSentVelocity.y(), mLastSentVelocity.x());
|
|
auto newTheta = std::atan2(velocity.y(), velocity.x());
|
|
|
|
//If we divert too much from where we need to go we must adjust.
|
|
if (std::abs(currentTheta - newTheta) > WFMath::numeric_constants<double>::pi() / 20) {
|
|
shouldSend = true;
|
|
}
|
|
}
|
|
} else {
|
|
//If we've never sent a movement update before we should do that now.
|
|
shouldSend = true;
|
|
}
|
|
}
|
|
if (shouldSend) {
|
|
//If we're moving to a certain destination and aren't avoiding anything we should tell the server to move to the destination.
|
|
if (destination.isValid() && !avoiding) {
|
|
result.destination = WFMath::Point<3>(destination.x(), mAvatar.m_location.m_pos.y(), destination.y());
|
|
}
|
|
result.direction = WFMath::Vector<3>(velocity.x(), 0, velocity.y());
|
|
mLastSentVelocity = velocity;
|
|
mExpectingServerMovement = true;
|
|
}
|
|
}
|
|
} else {
|
|
//We are steering, but the path is empty, which means we can't find any path. If we're moving we should stop movement.
|
|
//But we won't stop steering; perhaps we'll find a path later.
|
|
if (mLastSentVelocity != WFMath::Vector<2>::ZERO()) {
|
|
result.direction = WFMath::Vector<3>::ZERO();
|
|
mLastSentVelocity = WFMath::Vector<2>::ZERO();
|
|
mExpectingServerMovement = true;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void Steering::Awareness_TileUpdated(int tx, int ty)
|
|
{
|
|
mUpdateNeeded = true;
|
|
}
|
|
|
|
bool Steering::getIsExpectingServerMovement() const
|
|
{
|
|
return mExpectingServerMovement;
|
|
}
|
|
|
|
void Steering::setIsExpectingServerMovement(bool expected)
|
|
{
|
|
mExpectingServerMovement = expected;
|
|
}
|
|
|