Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

Ignore:
Timestamp:
May 31, 2010, 5:31:50 AM (15 years ago)
Author:
landauf
Message:

merged ai branch to presentation3

Location:
code/branches/presentation3
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • code/branches/presentation3

  • code/branches/presentation3/src/orxonox/controllers/ArtificialController.cc

    r7031 r7034  
    2323 *      Fabian 'x3n' Landau
    2424 *   Co-authors:
    25  *      ...
     25 *      Dominik Solenicki
    2626 *
    2727 */
     
    2929#include "ArtificialController.h"
    3030
     31#include <vector>
    3132#include "core/CoreIncludes.h"
     33#include "core/XMLPort.h"
    3234#include "worldentities/ControllableEntity.h"
    3335#include "worldentities/pawns/Pawn.h"
     
    3638#include "gametypes/Dynamicmatch.h"
    3739#include "controllers/WaypointPatrolController.h"
     40#include "controllers/NewHumanController.h"
     41#include "controllers/DroneController.h"
     42#include "util/Math.h"
     43#include "core/ConsoleCommand.h"
    3844
    3945namespace orxonox
    4046{
     47    SetConsoleCommand(ArtificialController, formationflight, true);
     48    SetConsoleCommand(ArtificialController, masteraction, true);
     49    SetConsoleCommand(ArtificialController, followme, true);
     50    SetConsoleCommand(ArtificialController, passivebehaviour, true);
     51    SetConsoleCommand(ArtificialController, formationsize, true);
     52
     53    static const unsigned int STANDARD_MAX_FORMATION_SIZE = 7;
     54    static const int FORMATION_LENGTH =  130;
     55    static const int FORMATION_WIDTH =  110;
     56    static const int FREEDOM_COUNT = 4; //seconds the slaves in a formation will be set free when master attacks an enemy
     57    static const float SPEED_MASTER = 0.6f;
     58    static const float ROTATEFACTOR_MASTER = 0.2f;
     59    static const float SPEED_FREE = 0.8f;
     60    static const float ROTATEFACTOR_FREE = 0.8f;
     61    static const int SECONDS_TO_FOLLOW_HUMAN = 100;
     62
    4163    ArtificialController::ArtificialController(BaseObject* creator) : Controller(creator)
    4264    {
     
    4466
    4567        this->target_ = 0;
     68        this->formationFlight_ = true;
     69        this->passive_ = false;
     70        this->maxFormationSize_ = STANDARD_MAX_FORMATION_SIZE;
     71        this->myMaster_ = 0;
     72        this->freedomCount_ = 0;
     73        this->team_ = -1;
     74        this->state_ = FREE;
     75        this->specificMasterAction_ = NONE;
     76        this->specificMasterActionHoldCount_  = 0;
    4677        this->bShooting_ = false;
    4778        this->bHasTargetPosition_ = false;
    4879        this->targetPosition_ = Vector3::ZERO;
     80        this->humanToFollow_ = NULL;
    4981
    5082        this->target_.setCallback(createFunctor(&ArtificialController::targetDied, this));
     
    5486    {
    5587    }
     88
     89    void ArtificialController::XMLPort(Element& xmlelement, XMLPort::Mode mode)
     90    {
     91        SUPER(ArtificialController, XMLPort, xmlelement, mode);
     92
     93        XMLPortParam(ArtificialController, "team", setTeam, getTeam, xmlelement, mode).defaultValues(-1);
     94        XMLPortParam(ArtificialController, "formationflight", setFormationFlight, getFormationFlight, xmlelement, mode).defaultValues(true);
     95        XMLPortParam(ArtificialController, "formation_size", setFormationSize, getFormationSize, xmlelement, mode).defaultValues(STANDARD_MAX_FORMATION_SIZE);
     96    }
     97
     98// Documentation only here to get a faster overview for creating a useful documentation...
     99
     100    /**
     101        @brief Activates / deactivates formationflight behaviour
     102        @param form activate formflight if form is true
     103    */
     104    void ArtificialController::formationflight(bool form)
     105    {
     106        for (ObjectList<Pawn>::iterator it = ObjectList<Pawn>::begin(); it; ++it)
     107        {
     108            if (!it->getController())
     109                continue;
     110
     111            ArtificialController *aiController = orxonox_cast<ArtificialController*>(it->getController());
     112
     113            if(aiController)
     114            {
     115                aiController->formationFlight_ = form;
     116                if(!form)
     117                {
     118                    if(aiController->state_ == MASTER) aiController->freeSlaves();
     119                    aiController->state_ = FREE;
     120                }
     121            }
     122        }
     123    }
     124
     125    /**
     126        @brief Get all masters to do a "specific master action"
     127        @param action which action to perform (integer, so it can be called with a console command (tmp solution))
     128    */
     129    void ArtificialController::masteraction(int action)
     130    {
     131        for (ObjectList<Pawn>::iterator it = ObjectList<Pawn>::begin(); it; ++it)
     132        {
     133            if (!it->getController())
     134                continue;
     135
     136            ArtificialController *aiController = orxonox_cast<ArtificialController*>(it->getController());
     137
     138            if(aiController && aiController->state_ == MASTER)
     139            {
     140                if (action == 1)
     141                    aiController->spinInit();
     142                if (action == 2)
     143                    aiController->turn180Init();
     144            }
     145        }
     146    }
     147
     148    /**
     149        @brief A human player gets followed by its nearest master. Initiated by console command, intended for demonstration puproses. Does not work at the moment.
     150    */
     151    void ArtificialController::followme()
     152    {
     153
     154        Pawn *humanPawn = NULL;
     155        NewHumanController *currentHumanController = NULL;
     156        std::vector<ArtificialController*> allMasters;
     157
     158        for (ObjectList<Pawn>::iterator it = ObjectList<Pawn>::begin(); it; ++it)
     159        {
     160            if (!it->getController())
     161                continue;
     162
     163            currentHumanController = orxonox_cast<NewHumanController*>(it->getController());
     164
     165            if(currentHumanController) humanPawn = *it;
     166
     167            ArtificialController *aiController = orxonox_cast<ArtificialController*>(it->getController());
     168
     169            if(aiController || aiController->state_ == MASTER)
     170                allMasters.push_back(aiController);
     171
     172        }
     173
     174        if((humanPawn != NULL) && (allMasters.size() != 0))
     175        {
     176                float posHuman = humanPawn->getPosition().length();
     177                float distance = 0.0f;
     178                float minDistance = FLT_MAX;
     179                int index = 0;
     180                int i = 0;
     181
     182                for(std::vector<ArtificialController*>::iterator it = allMasters.begin(); it != allMasters.end(); it++)
     183                    {
     184                        distance = posHuman - (*it)->getControllableEntity()->getPosition().length();
     185                        if(distance < minDistance) index = i;
     186                    }
     187                allMasters[index]->humanToFollow_ = humanPawn;
     188//                allMasters[index]->followHuman(humanPawn, false);
     189            }
     190
     191    }
     192
     193    /**
     194        @brief Sets shooting behaviour of pawns.
     195        @param passive if true, bots won't shoot.
     196    */
     197    void ArtificialController::passivebehaviour(bool passive)
     198    {
     199        for (ObjectList<Pawn>::iterator it = ObjectList<Pawn>::begin(); it; ++it)
     200        {
     201            if (!it->getController())
     202                continue;
     203
     204            ArtificialController *aiController = orxonox_cast<ArtificialController*>(it->getController());
     205
     206            if(aiController)
     207            {
     208                aiController->passive_ = passive;
     209            }
     210        }
     211    }
     212
     213
     214    /**
     215        @brief Sets maximal formation size
     216        @param size maximal formation size.
     217    */
     218    void ArtificialController::formationsize(int size)
     219    {
     220        for (ObjectList<Pawn>::iterator it = ObjectList<Pawn>::begin(); it; ++it)
     221        {
     222            if (!it->getController())
     223                continue;
     224
     225            ArtificialController *aiController = orxonox_cast<ArtificialController*>(it->getController());
     226
     227            if(aiController)
     228            {
     229                aiController->maxFormationSize_ = size;
     230            }
     231        }
     232    }
     233
     234    /**
     235        @brief Gets called when ControllableEntity is being changed. Resets the bot when it dies.
     236    */
     237    void ArtificialController::changedControllableEntity()
     238    {
     239        if(!getControllableEntity())
     240        {
     241        if (this->state_ == SLAVE) unregisterSlave();
     242         if (this->state_ == MASTER) setNewMasterWithinFormation();
     243        this->slaves_.clear();
     244        this->state_ = FREE;
     245
     246        }
     247    }
     248
    56249
    57250    void ArtificialController::moveToPosition(const Vector3& target)
     
    63256        float distance = (target - this->getControllableEntity()->getPosition()).length();
    64257
    65         if (this->target_ || distance > 10)
    66         {
    67             // Multiply with 0.8 to make them a bit slower
    68             this->getControllableEntity()->rotateYaw(-0.8f * sgn(coord.x) * coord.x*coord.x);
    69             this->getControllableEntity()->rotatePitch(0.8f * sgn(coord.y) * coord.y*coord.y);
    70         }
    71 
    72         if (this->target_ && distance < 200 && this->getControllableEntity()->getVelocity().squaredLength() > this->target_->getVelocity().squaredLength())
    73             this->getControllableEntity()->moveFrontBack(-0.5f); // They don't brake with full power to give the player a chance
    74         else
    75             this->getControllableEntity()->moveFrontBack(0.8f);
     258
     259        if(this->state_ == FREE)
     260        {
     261            if (this->target_ || distance > 10)
     262            {
     263                // Multiply with 0.8 to make them a bit slower
     264                this->getControllableEntity()->rotateYaw(-1.0f * ROTATEFACTOR_FREE * sgn(coord.x) * coord.x*coord.x);
     265                this->getControllableEntity()->rotatePitch(ROTATEFACTOR_FREE * sgn(coord.y) * coord.y*coord.y);
     266            }
     267
     268            if (this->target_ && distance < 200 && this->getControllableEntity()->getVelocity().squaredLength() > this->target_->getVelocity().squaredLength())
     269            {
     270              this->getControllableEntity()->moveFrontBack(-0.05f); // They don't brake with full power to give the player a chance
     271            } else this->getControllableEntity()->moveFrontBack(SPEED_FREE);
     272        }
     273
     274
     275
     276        if(this->state_ == MASTER)
     277        {
     278            if (this->target_ || distance > 10)
     279            {
     280                this->getControllableEntity()->rotateYaw(-1.0f * ROTATEFACTOR_MASTER * sgn(coord.x) * coord.x*coord.x);
     281                this->getControllableEntity()->rotatePitch(ROTATEFACTOR_MASTER * sgn(coord.y) * coord.y*coord.y);
     282            }
     283
     284            if (this->target_ && distance < 200 && this->getControllableEntity()->getVelocity().squaredLength() > this->target_->getVelocity().squaredLength())
     285            {
     286                this->getControllableEntity()->moveFrontBack(-0.05f);
     287            } else this->getControllableEntity()->moveFrontBack(SPEED_MASTER);
     288        }
     289
     290
     291
     292        if(this->state_ == SLAVE)
     293        {
     294
     295           this->getControllableEntity()->rotateYaw(-2.0f * ROTATEFACTOR_MASTER * sgn(coord.x) * coord.x*coord.x);
     296           this->getControllableEntity()->rotatePitch(2.0f * ROTATEFACTOR_MASTER * sgn(coord.y) * coord.y*coord.y);
     297
     298            if (distance < 300)
     299            {
     300                if (distance < 40)
     301                {
     302                    this->getControllableEntity()->moveFrontBack(0.8f*SPEED_MASTER);
     303                } else this->getControllableEntity()->moveFrontBack(1.2f*SPEED_MASTER);
     304
     305            } else {
     306                this->getControllableEntity()->moveFrontBack(1.2f*SPEED_MASTER + distance/300.0f);
     307            }
     308        }
    76309    }
    77310
     
    80313        this->moveToPosition(this->targetPosition_);
    81314    }
     315
     316    int ArtificialController::getState()
     317    {
     318        return this->state_;
     319    }
     320
     321    /**
     322        @brief Unregisters a slave from its master. Called by a slave.
     323    */
     324    void ArtificialController::unregisterSlave() {
     325        if(myMaster_)
     326        {
     327            std::vector<ArtificialController*>::iterator it = std::find(myMaster_->slaves_.begin(), myMaster_->slaves_.end(), this);
     328            if( it != myMaster_->slaves_.end() )
     329                myMaster_->slaves_.erase(it);
     330        }
     331    }
     332
     333    void ArtificialController::searchNewMaster()
     334    {
     335
     336        if (!this->getControllableEntity())
     337            return;
     338
     339        this->targetPosition_ = this->getControllableEntity()->getPosition();
     340        this->forgetTarget();
     341        int teamSize = 0;
     342        //go through all pawns
     343        for (ObjectList<Pawn>::iterator it = ObjectList<Pawn>::begin(); it; ++it)
     344        {
     345
     346            //same team?
     347            if (!ArtificialController::sameTeam(this->getControllableEntity(), static_cast<ControllableEntity*>(*it), this->getGametype()))
     348                continue;
     349
     350            //has it an ArtificialController?
     351            if (!it->getController())
     352                continue;
     353
     354            //is pawn oneself?
     355            if (orxonox_cast<ControllableEntity*>(*it) == this->getControllableEntity())
     356                continue;
     357
     358            teamSize++;
     359
     360            ArtificialController *newMaster = orxonox_cast<ArtificialController*>(it->getController());
     361
     362            //is it a master?
     363            if (!newMaster || newMaster->getState() != MASTER)
     364                continue;
     365
     366            float distance = (it->getPosition() - this->getControllableEntity()->getPosition()).length();
     367
     368            // is pawn in range?
     369            if (distance < 5000)
     370            {
     371                if(newMaster->slaves_.size() > this->maxFormationSize_) continue;
     372
     373                for(std::vector<ArtificialController*>::iterator itSlave = this->slaves_.begin(); itSlave != this->slaves_.end(); itSlave++)
     374                {
     375                    (*itSlave)->myMaster_ = newMaster;
     376                    newMaster->slaves_.push_back(*itSlave);
     377                }
     378                this->slaves_.clear();
     379                this->state_ = SLAVE;
     380
     381                this->myMaster_ = newMaster;
     382                newMaster->slaves_.push_back(this);
     383
     384                break;
     385            }
     386        }
     387
     388        if (state_ != SLAVE  && teamSize != 0) state_ = MASTER;
     389
     390    }
     391
     392    /**
     393        @brief Commands the slaves of a master into a formation. Called by a master.
     394    */
     395    void ArtificialController::commandSlaves()
     396    {
     397        if(this->state_ != MASTER) return;
     398
     399        Quaternion orient = this->getControllableEntity()->getOrientation();
     400        Vector3 dest = this->getControllableEntity()->getPosition();
     401
     402        // 1 slave: follow
     403        if (this->slaves_.size() == 1)
     404        {
     405            dest += 4*orient*WorldEntity::BACK;
     406            this->slaves_.front()->setTargetPosition(dest);
     407        }
     408        else
     409        {
     410            dest += 1.0f*orient*WorldEntity::BACK;
     411            Vector3 pos = Vector3::ZERO;
     412            int i = 1;
     413
     414            for(std::vector<ArtificialController*>::iterator it = slaves_.begin(); it != slaves_.end(); it++)
     415            {
     416                pos = Vector3::ZERO;
     417                if (i <= 1) pos += dest  + FORMATION_WIDTH*(orient*WorldEntity::LEFT);
     418                if (i == 2) pos += dest  + FORMATION_WIDTH*(orient*WorldEntity::RIGHT);
     419                if (i == 3) pos += dest  + FORMATION_WIDTH*(orient*WorldEntity::UP);
     420                if (i >= 4)
     421                {
     422                    pos += dest  + FORMATION_WIDTH*(orient*WorldEntity::DOWN);
     423                    i = 1;
     424                    dest += FORMATION_LENGTH*(orient*WorldEntity::BACK);
     425                    (*it)->setTargetPosition(pos);
     426                    continue;
     427                }
     428                i++;
     429                (*it)->setTargetPosition(pos);
     430            }
     431        }
     432    }
     433
     434    /**
     435        @brief Sets a new master within the formation. Called by a master.
     436    */
     437    void ArtificialController::setNewMasterWithinFormation()
     438    {
     439        if(this->state_ != MASTER) return;
     440
     441        if (this->slaves_.empty())
     442            return;
     443
     444        ArtificialController *newMaster = this->slaves_.back();
     445        this->slaves_.pop_back();
     446
     447        if(!newMaster) return;
     448        newMaster->state_ = MASTER;
     449        newMaster->slaves_ = this->slaves_;
     450
     451        this->slaves_.clear();
     452        this->state_ = SLAVE;
     453        this->myMaster_ = newMaster;
     454
     455        for(std::vector<ArtificialController*>::iterator it = newMaster->slaves_.begin(); it != newMaster->slaves_.end(); it++)
     456        {
     457            (*it)->myMaster_ = newMaster;
     458        }
     459
     460    }
     461
     462    /**
     463        @brief Frees all slaves form a master. Called by a master.
     464    */
     465    void ArtificialController::freeSlaves()
     466    {
     467        if(this->state_ != MASTER) return;
     468
     469        for(std::vector<ArtificialController*>::iterator it = slaves_.begin(); it != slaves_.end(); it++)
     470        {
     471            (*it)->state_ = FREE;
     472        }
     473        this->slaves_.clear();
     474    }
     475
     476    /**
     477        @brief Master sets its slaves free for @var FREEDOM_COUNT seconds.
     478    */
     479    void ArtificialController::forceFreeSlaves()
     480    {
     481        if(this->state_ != MASTER) return;
     482
     483        for(std::vector<ArtificialController*>::iterator it = slaves_.begin(); it != slaves_.end(); it++)
     484        {
     485            (*it)->state_ = FREE;
     486            (*it)->forceFreedom();
     487            (*it)->targetPosition_ = this->targetPosition_;
     488            (*it)->bShooting_ = true;
     489//             (*it)->getControllableEntity()->fire(0);// fire once for fun
     490        }
     491    }
     492
     493    void ArtificialController::loseMasterState()
     494    {
     495        this->freeSlaves();
     496        this->state_ = FREE;
     497    }
     498
     499
     500    void ArtificialController::forceFreedom()
     501    {
     502        this->freedomCount_ = FREEDOM_COUNT;
     503    }
     504
     505    /**
     506        @brief Checks wether caller has been forced free, decrements time to stay forced free.
     507        @return true if forced free.
     508    */
     509    bool ArtificialController::forcedFree()
     510    {
     511        if(this->freedomCount_ > 0)
     512        {
     513            this->freedomCount_--;
     514            return true;
     515        } else return false;
     516    }
     517
     518    /**
     519        @brief Used to continue a "specific master action" for a certain time and resuming normal behaviour after.
     520    */
     521    void ArtificialController::specificMasterActionHold()
     522    {
     523        if(this->state_ != MASTER) return;
     524
     525        if (specificMasterActionHoldCount_ == 0)
     526         {
     527            this->specificMasterAction_ = NONE;
     528            this->searchNewTarget();
     529         }
     530        else specificMasterActionHoldCount_--;
     531    }
     532
     533    /**
     534        @brief Master initializes a 180 degree turn. Leads to a "specific master action".
     535    */
     536    void ArtificialController::turn180Init()
     537    {
     538        COUT(0) << "~turnInit" << std::endl;
     539        if(this->state_ != MASTER) return;
     540
     541        Quaternion orient = this->getControllableEntity()->getOrientation();
     542
     543        this->setTargetPosition(this->getControllableEntity()->getPosition() + 1000.0f*orient*WorldEntity::BACK);
     544
     545        this->specificMasterActionHoldCount_ = 4;
     546
     547        this->specificMasterAction_ = TURN180;
     548    }
     549
     550    /**
     551        @brief Execute the 180 degree turn. Called within tick.
     552    */
     553    void ArtificialController::turn180()
     554    {
     555            Vector2 coord = get2DViewdirection(this->getControllableEntity()->getPosition(), this->getControllableEntity()->getOrientation() * WorldEntity::FRONT, this->getControllableEntity()->getOrientation() * WorldEntity::UP, this->targetPosition_);
     556
     557            this->getControllableEntity()->rotateYaw(-2.0f * sgn(coord.x) * coord.x*coord.x);
     558            this->getControllableEntity()->rotatePitch(2.0f * sgn(coord.y) * coord.y*coord.y);
     559
     560            this->getControllableEntity()->moveFrontBack(SPEED_MASTER);
     561    }
     562
     563    /**
     564        @brief Master initializes a spin around its looking direction axis. Leads to a "specific master action". Not yet implemented.
     565    */
     566    void ArtificialController::spinInit()
     567    {
     568        COUT(0) << "~spinInit" << std::endl;
     569        if(this->state_ != MASTER) return;
     570        this->specificMasterAction_ = SPIN;
     571        this->specificMasterActionHoldCount_ = 10;
     572    }
     573
     574    /**
     575        @brief Execute the spin. Called within tick.
     576    */
     577    void ArtificialController::spin()
     578    {
     579            this->moveToTargetPosition();
     580            this->getControllableEntity()->rotateRoll(0.8f);
     581    }
     582
     583    /**
     584        @brief Master begins to follow a human player. Is a "specific master action".
     585        @param humanController human to follow.
     586        @param alaways follows human forever if true, else it follows it for @var SECONDS_TO_FOLLOW_HUMAN seconds.
     587    */
     588    void ArtificialController::followHumanInit(Pawn* human, bool always)
     589    {
     590        COUT(0) << "~followInit" << std::endl;
     591        if (human == NULL || this->state_ != MASTER)
     592            return;
     593
     594        this->specificMasterAction_  =  FOLLOWHUMAN;
     595
     596        this->setTarget(human);
     597        if (!always)
     598            this->specificMasterActionHoldCount_ = SECONDS_TO_FOLLOW_HUMAN;
     599        else
     600            this->specificMasterActionHoldCount_ = INT_MAX; //for now...
     601
     602    }
     603
     604    /**
     605        @brief Follows target with adjusted speed. Called within tick.
     606    */
     607    void ArtificialController::follow()
     608    {
     609        this->moveToTargetPosition(); //standard position apprach for now.
     610    }
     611
     612
    82613
    83614    void ArtificialController::setTargetPosition(const Vector3& target)
     
    145676        this->bHasTargetPosition_ = (this->targetPosition_ != Vector3::ZERO);
    146677
    147         Pawn* pawn = dynamic_cast<Pawn*>(this->getControllableEntity());
     678        Pawn* pawn = orxonox_cast<Pawn*>(this->getControllableEntity());
    148679        if (pawn)
    149680            pawn->setAimPosition(this->targetPosition_);
     
    189720        int team2 = -1;
    190721
    191         if (entity1->getXMLController())
    192         {
    193             WaypointPatrolController* wpc = orxonox_cast<WaypointPatrolController*>(entity1->getXMLController());
    194             if (wpc)
    195                 team1 = wpc->getTeam();
    196         }
    197         if (entity2->getXMLController())
    198         {
    199             WaypointPatrolController* wpc = orxonox_cast<WaypointPatrolController*>(entity2->getXMLController());
    200             if (wpc)
    201                 team2 = wpc->getTeam();
     722        Controller* controller = 0;
     723        if (entity1->getController())
     724            controller = entity1->getController();
     725        else
     726            controller = entity1->getXMLController();
     727        if (controller)
     728        {
     729            ArtificialController* ac = orxonox_cast<ArtificialController*>(controller);
     730            if (ac)
     731                team1 = ac->getTeam();
     732        }
     733
     734        if (entity1->getController())
     735            controller = entity1->getController();
     736        else
     737            controller = entity1->getXMLController();
     738        if (controller)
     739        {
     740            ArtificialController* ac = orxonox_cast<ArtificialController*>(controller);
     741            if (ac)
     742                team2 = ac->getTeam();
    202743        }
    203744
     
    246787        }
    247788
     789        DroneController* droneController = 0;
     790        droneController = orxonox_cast<DroneController*>(entity1->getController());
     791        if (droneController && static_cast<ControllableEntity*>(droneController->getOwner()) == entity2)
     792            return true;
     793        droneController = orxonox_cast<DroneController*>(entity2->getController());
     794        if (droneController && static_cast<ControllableEntity*>(droneController->getOwner()) == entity1)
     795            return true;
     796        DroneController* droneController1 = orxonox_cast<DroneController*>(entity1->getController());
     797        DroneController* droneController2 = orxonox_cast<DroneController*>(entity2->getController());
     798        if (droneController1 && droneController2 && droneController1->getOwner() == droneController2->getOwner())
     799            return true;
     800
    248801        Dynamicmatch* dynamic = orxonox_cast<Dynamicmatch*>(gametype);
    249802        if (dynamic)
Note: See TracChangeset for help on using the changeset viewer.