// Cyphesis Online RPG Server and AI Engine
// Copyright (C) 2000,2001 Alistair Riddoch
//
// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

// $Id: Pedestrian.cpp,v 1.83 2007-09-22 15:46:14 alriddoch Exp $

#include "Pedestrian.h"

#include "Entity.h"

#include "common/const.h"
#include "common/debug.h"
#include "common/log.h"
#include "common/compose.hpp"

#include "common/Tick.h"

#include <wfmath/atlasconv.h>

#include <Atlas/Objects/Operation.h>
#include <Atlas/Objects/Anonymous.h>

using Atlas::Message::Element;
using Atlas::Objects::Operation::Move;
using Atlas::Objects::Entity::Anonymous;

static const bool debug_flag = false;

/// \brief Constructor
///
/// @param body the Entity this Movement is tracking.
Pedestrian::Pedestrian(Entity & body) : Movement(body)
{
}

Pedestrian::~Pedestrian()
{
}

double Pedestrian::getTickAddition(const Point3D & coordinates,
                                   const Vector3D & velocity) const
{
    // This may seem a little weird. Everything is handled in squares to
    // reduce the number of square roots that have to be calculated. In
    // this case only one is required.
    if (m_targetPos.isValid()) {
        double basic_square_distance = velocity.sqrMag()
                                       * consts::square_basic_tick;
        double square_distance = squareDistance(coordinates, m_targetPos);
        debug( std::cout << "basic_distance: " << basic_square_distance
                         << std::endl << std::flush;);
        debug( std::cout << "distance: " << square_distance << std::endl
                         << std::flush;);
        if (basic_square_distance > square_distance) {
            debug( std::cout << "\tshortened tick" << std::endl << std::flush;);
            return sqrt(square_distance / basic_square_distance)
                        * consts::basic_tick;
        }
    }
    return consts::basic_tick;
}

/// \brief Calculate the updated position of the entity since the last
/// move operation.
///
/// The does not modify the entity, but determines a new Location based
/// on the velocity of the entity, time elapsed, whether any collision
/// occurs, and whether the entity has reached its target.
/// @return 1 if no update was made, or 0 otherwise
int Pedestrian::getUpdatedLocation(Location & return_location)
{
    const double & current_time = BaseWorld::instance().getTime();
    double time_diff = current_time - m_body.m_location.timeStamp();

    if (!updateNeeded(m_body.m_location)) {
        debug( std::cout << "No update" << std::endl << std::flush;);
        return 1;
    }

    Location new_location(m_body.m_location);

    // Update location
    Point3D new_coords = m_body.m_location.pos();
    new_coords += (m_body.m_location.velocity() * time_diff);
    if (m_targetPos.isValid()) {
        Point3D new_coords2 = new_coords;
        new_coords2 += (m_body.m_location.velocity() * (consts::basic_tick / 10.0));
        // The values returned by squareDistance are squares, so
        // cannot be used except for comparison
        double dist = squareDistance(m_targetPos, new_coords);
        double dist2 = squareDistance(m_targetPos, new_coords2);
        debug( std::cout << "dist: " << dist << "," << dist2 << std::endl
                         << std::flush;);
        if (dist2 > dist) {
            // If dist2 is larger than dist, then further movement
            // is away from m_targetPos, so we know we have arrived.
            debug( std::cout << "m_targetPos achieved";);
            new_location.m_pos = m_targetPos;

            // We have arrived at our target position and must
            // stop, or be deflected
            // Arrived at intended destination
            reset();
            new_location.m_velocity = Vector3D(0,0,0);
        } else {
            new_location.m_pos = new_coords;
        }
    } else {
        new_location.m_pos = new_coords;
    }

    std::string mode("standing");

    if (m_body.hasAttr("mode")) {
        Element mode_attr;
        m_body.getAttr("mode", mode_attr);
        if (mode_attr.isString()) {
            mode = mode_attr.String();
        } else {
            log(ERROR, String::compose("MODE on entity is a \"%1\" "
                                       "in Pedestrain::getUpdatedLocation.",
                                       Element::typeName(mode_attr.getType())));
        }
    }

    float z = BaseWorld::instance().constrainHeight(new_location.m_loc, new_location.m_pos,
                                              mode);
    debug(std::cout << "Height adjustment " << z << " " << new_location.m_pos.z()
                    << std::endl << std::flush;);

    new_location.m_pos.z() = z;

    return_location = new_location;

    return 0;
}

Operation Pedestrian::generateMove(const Location & new_location)
{
    // Create move operation
    Move moveOp;
    moveOp->setTo(m_body.getId());

    // Set up argument for operation
    Anonymous move_arg;
    move_arg->setId(m_body.getId());

    // Walk out what the mode of the character should be.
    // Performed in squares to save on that critical sqrt() call
    double vel_square_mag = 0;
    if (new_location.velocity().isValid()) {
        vel_square_mag = new_location.velocity().sqrMag();
    }
    double square_speed_ratio = vel_square_mag / consts::square_base_velocity;

    float height = 0;
    if (m_body.m_location.bBox().isValid()) {
        height = m_body.m_location.bBox().highCorner().z() - 
                 m_body.m_location.bBox().lowCorner().z();
    }

    if (new_location.pos().z() < (0 - height * 0.75)) {
        move_arg->setAttr("mode", "swimming");
    } else {
        if (square_speed_ratio > 0.25) { // 0.5 ^ 2
            move_arg->setAttr("mode", "running");
        } else if (square_speed_ratio > 0.0025) { // 0.05 ^ 2
            move_arg->setAttr("mode", "walking");
        } else {
            move_arg->setAttr("mode", "standing");
        }
    }
    debug(std::cout << move_arg->getAttr("mode").asString() << std::endl << std::flush;);

    new_location.addToEntity(move_arg);
    moveOp->setArgs1(move_arg);

    return moveOp;
}


syntax highlighted by Code2HTML, v. 0.9.1